Example #1
0
 def __init__(self, cr):
     if hasattr(base, 'race') and base.race:
         base.race.delete()
     self.qbox = loader.loadModel('phase_6/models/karting/qbox')
     self.boostArrowTexture = loader.loadTexture('phase_6/maps/boost_arrow.jpg', 'phase_6/maps/boost_arrow_a.rgb')
     self.boostArrowTexture.setMinfilter(Texture.FTLinear)
     DistributedObject.DistributedObject.__init__(self, cr)
     self.kartMap = {}
     self.fsm = ClassicFSM.ClassicFSM('Race', [State.State('join', self.enterJoin, self.exitJoin, ['prep', 'leave']),
      State.State('prep', self.enterPrep, self.exitPrep, ['tutorial', 'leave']),
      State.State('tutorial', self.enterTutorial, self.exitTutorial, ['start', 'waiting', 'leave']),
      State.State('waiting', self.enterWaiting, self.exitWaiting, ['start', 'leave']),
      State.State('start', self.enterStart, self.exitStart, ['racing', 'leave']),
      State.State('racing', self.enterRacing, self.exitRacing, ['finished', 'leave']),
      State.State('finished', self.enterFinished, self.exitFinished, ['leave']),
      State.State('leave', self.enterLeave, self.exitLeave, [])], 'join', 'leave')
     self.gui = RaceGUI(self)
     base.race = self
     self.currT = 0
     self.currLapT = 0
     self.currGag = 0
     self.tdelay = 0
     self.finished = False
     self.thrownGags = []
     self.effectManager = EffectManager.EffectManager()
     self.piejectileManager = PiejectileManager.PiejectileManager()
     self.lastTimeUpdate = globalClock.getFrameTime()
     self.initGags()
     self.canShoot = True
     self.isUrbanTrack = False
     self.hasFog = False
     self.dummyNode = None
     self.fog = None
     self.bananaSound = base.loader.loadSfx('phase_6/audio/sfx/KART_tossBanana.ogg')
     self.anvilFall = base.loader.loadSfx('phase_6/audio/sfx/KART_Gag_Hit_Anvil.ogg')
     self.accept('leaveRace', self.leaveRace)
     self.accept('finishRace', self.finishRace)
     self.toonsToLink = []
     self.curveTs = []
     self.curvePoints = []
     self.localKart = None
     self.musicTrack = None
     self.victory = None
     self.miscTaskNames = []
     self.boostDir = {}
     self.knownPlace = {}
     self.placeFixup = []
     self.curve = None
     self.barricadeSegments = 100.0
     self.outerBarricadeDict = {}
     self.innerBarricadeDict = {}
     self.maxLap = 0
     self.oldT = 0
     self.debugIt = 0
     self.startPos = None
     return
 def __init__(self, cr):
     self.qbox = loader.loadModel("phase_6/models/karting/qbox")
     self.boostArrowTexture = loader.loadTexture("phase_6/maps/boost_arrow.jpg", "phase_6/maps/boost_arrow_a.rgb")
     self.boostArrowTexture.setMinfilter(Texture.FTLinear)
     DistributedObject.DistributedObject.__init__(self, cr)
     self.kartMap = {}
     self.fsm = ClassicFSM.ClassicFSM(
         "Race",
         [
             State.State("join", self.enterJoin, self.exitJoin, ["prep", "leave"]),
             State.State("prep", self.enterPrep, self.exitPrep, ["tutorial", "leave"]),
             State.State("tutorial", self.enterTutorial, self.exitTutorial, ["start", "waiting", "leave"]),
             State.State("waiting", self.enterWaiting, self.exitWaiting, ["start", "leave"]),
             State.State("start", self.enterStart, self.exitStart, ["racing", "leave"]),
             State.State("racing", self.enterRacing, self.exitRacing, ["finished", "leave"]),
             State.State("finished", self.enterFinished, self.exitFinished, ["leave"]),
             State.State("leave", self.enterLeave, self.exitLeave, []),
         ],
         "join",
         "leave",
     )
     self.gui = RaceGUI(self)
     base.race = self
     self.currT = 0
     self.currLapT = 0
     self.currGag = 0
     self.tdelay = 0
     self.finished = False
     self.thrownGags = []
     self.effectManager = EffectManager.EffectManager()
     self.piejectileManager = PiejectileManager.PiejectileManager()
     self.lastTimeUpdate = globalClock.getFrameTime()
     self.initGags()
     self.canShoot = True
     self.isUrbanTrack = False
     self.hasFog = False
     self.dummyNode = None
     self.fog = None
     self.bananaSound = base.loadSfx("phase_6/audio/sfx/KART_tossBanana.ogg")
     self.anvilFall = base.loadSfx("phase_6/audio/sfx/KART_Gag_Hit_Anvil.ogg")
     self.accept("leaveRace", self.leaveRace)
     self.toonsToLink = []
     self.curveTs = []
     self.curvePoints = []
     self.localKart = None
     self.musicTrack = None
     self.victory = None
     self.miscTaskNames = []
     self.boostDir = {}
     self.knownPlace = {}
     self.placeFixup = []
     self.curve = None
     self.barricadeSegments = 100.0
     self.outerBarricadeDict = {}
     self.innerBarricadeDict = {}
     self.maxLap = 0
     self.oldT = 0
     self.debugIt = 0
     self.startPos = None
     return
class DistributedRace(DistributedObject.DistributedObject):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRace')
    ReadyPost = 'RaceReady'
    WinEvent = 'RaceWinEvent'
    BGM_BaseDir = 'phase_6/audio/bgm/'
    SFX_BaseDir = 'phase_6/audio/sfx/'
    SFX_StartBoop = SFX_BaseDir + 'KART_raceStart1.ogg'
    SFX_StartBoop2 = SFX_BaseDir + 'KART_raceStart2.ogg'
    SFX_Applause = SFX_BaseDir + 'KART_Applause_%d.ogg'

    def __init__(self, cr):
        self.qbox = loader.loadModel('phase_6/models/karting/qbox')
        self.boostArrowTexture = loader.loadTexture(
            'phase_6/maps/boost_arrow.jpg', 'phase_6/maps/boost_arrow_a.rgb')
        self.boostArrowTexture.setMinfilter(Texture.FTLinear)
        DistributedObject.DistributedObject.__init__(self, cr)
        self.kartMap = {}
        self.fsm = ClassicFSM.ClassicFSM('Race', [
            State.State('join', self.enterJoin, self.exitJoin,
                        ['prep', 'leave']),
            State.State('prep', self.enterPrep, self.exitPrep,
                        ['tutorial', 'leave']),
            State.State('tutorial', self.enterTutorial, self.exitTutorial,
                        ['start', 'waiting', 'leave']),
            State.State('waiting', self.enterWaiting, self.exitWaiting,
                        ['start', 'leave']),
            State.State('start', self.enterStart, self.exitStart,
                        ['racing', 'leave']),
            State.State('racing', self.enterRacing, self.exitRacing,
                        ['finished', 'leave']),
            State.State('finished', self.enterFinished, self.exitFinished,
                        ['leave']),
            State.State('leave', self.enterLeave, self.exitLeave, [])
        ], 'join', 'leave')
        self.gui = RaceGUI(self)
        base.race = self
        self.currT = 0
        self.currLapT = 0
        self.currGag = 0
        self.tdelay = 0
        self.finished = False
        self.thrownGags = []
        self.effectManager = EffectManager.EffectManager()
        self.piejectileManager = PiejectileManager.PiejectileManager()
        self.lastTimeUpdate = globalClock.getFrameTime()
        self.initGags()
        self.canShoot = True
        self.isUrbanTrack = False
        self.hasFog = False
        self.dummyNode = None
        self.fog = None
        self.bananaSound = base.loadSfx(
            'phase_6/audio/sfx/KART_tossBanana.ogg')
        self.anvilFall = base.loadSfx(
            'phase_6/audio/sfx/KART_Gag_Hit_Anvil.ogg')
        self.accept('leaveRace', self.leaveRace)
        self.toonsToLink = []
        self.curveTs = []
        self.curvePoints = []
        self.localKart = None
        self.musicTrack = None
        self.victory = None
        self.miscTaskNames = []
        self.boostDir = {}
        self.knownPlace = {}
        self.placeFixup = []
        self.curve = None
        self.barricadeSegments = 100.0
        self.outerBarricadeDict = {}
        self.innerBarricadeDict = {}
        self.maxLap = 0
        self.oldT = 0
        self.debugIt = 0
        self.startPos = None
        return

    def generate(self):
        self.notify.debug('generate: %s' % self.doId)
        DistributedObject.DistributedObject.generate(self)
        bboard.post('race', self)
        self.roomWatcher = None
        self.cutoff = 0.01
        self.startBoopSfx = base.loadSfx(self.SFX_StartBoop)
        self.startBoop2Sfx = base.loadSfx(self.SFX_StartBoop2)
        return

    def announceGenerate(self):
        self.notify.debug('announceGenerate: %s' % self.doId)
        DistributedObject.DistributedObject.announceGenerate(self)
        musicFile = self.BGM_BaseDir + RaceGlobals.TrackDict[self.trackId][7]
        self.raceMusic = base.loadMusic(musicFile)
        base.playMusic(self.raceMusic, looping=1, volume=0.8)
        camera.reparentTo(render)
        if self.trackId in (RaceGlobals.RT_Urban_1, RaceGlobals.RT_Urban_1_rev,
                            RaceGlobals.RT_Urban_2,
                            RaceGlobals.RT_Urban_2_rev):
            self.isUrbanTrack = True
        self.oldFarPlane = base.camLens.getFar()
        base.camLens.setFar(12000)
        localAvatar.startPosHprBroadcast()
        localAvatar.d_broadcastPositionNow()
        DistributedSmoothNode.activateSmoothing(1, 1)
        self.reversed = self.trackId / 2.0 > int(self.trackId / 2.0)
        for i in range(3):
            base.loader.tick()

        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.sky.setPos(0, 0, 0)
        self.sky.setScale(20.0)
        self.sky.setFogOff()
        if self.trackId in (RaceGlobals.RT_Urban_1, RaceGlobals.RT_Urban_1_rev,
                            RaceGlobals.RT_Urban_2,
                            RaceGlobals.RT_Urban_2_rev):
            self.loadFog()
        self.setupGeom()
        self.startSky()
        for i in range(5):
            base.loader.tick()

    def disable(self):
        self.notify.debug('disable %s' % self.doId)
        if self.musicTrack:
            self.musicTrack.finish()
        self.raceMusic.stop()
        self.stopSky()
        if self.sky is not None:
            self.sky.removeNode()
        if self.dummyNode:
            self.dummyNode.removeNode()
            self.dummyNode = None
        for taskName in self.miscTaskNames:
            taskMgr.remove(taskName)

        taskMgr.remove('raceWatcher')
        self.ignoreAll()
        DistributedSmoothNode.activateSmoothing(1, 0)
        if self.isUrbanTrack:
            self.unloadUrbanTrack()
        if self.fog:
            render.setFogOff()
            del self.fog
            self.fog = None
        if self.geom is not None:
            self.geom.hide()
        base.camLens.setFar(self.oldFarPlane)
        DistributedObject.DistributedObject.disable(self)
        return

    def delete(self):
        self.notify.debug('delete %s' % self.doId)
        if self.gui:
            self.gui.destroy()
            self.gui = None
        if self.geom is not None:
            self.geom.removeNode()
            self.geom = None
            for i in self.gags:
                i.delete()
                del i

        self.piejectileManager.delete()
        if self.curveTs:
            del self.curveTs
        if self.curvePoints:
            del self.curvePoints
        if self.curve:
            del self.curve
        if self.victory:
            del self.victory
        del self.fsm
        del self.anvilFall
        del self.bananaSound
        del self.localKart
        DistributedObject.DistributedObject.delete(self)
        taskMgr.remove(self.uniqueName('countdownTimerTask'))
        taskMgr.remove('raceWatcher')
        bboard.remove('race')
        self.ignoreAll()
        del base.race
        return

    def d_requestThrow(self, x, y, z):
        self.sendUpdate('requestThrow', [x, y, z])

    def d_requestKart(self):
        self.sendUpdate('requestKart', [])

    def waitingForJoin(self):
        self.notify.debug('I got the barrier')
        self.fsm.enterInitialState()

    def racerDisconnected(self, avId):
        self.notify.debug('lost racer: %s' % avId)
        if avId in self.kartMap:
            if avId in self.toonsToLink:
                self.toonsToLink.remove(avId)
            toon = base.cr.doId2do.get(avId, None)
            kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
            self.avIds.remove(avId)
            del self.kartMap[avId]
            self.gui.racerLeft(avId, unexpected=True)
            if kart:
                kart.reparentTo(hidden)
            if toon:
                toon.reparentTo(hidden)
            if len(self.toonsToLink) == 0:
                self.doneBarrier('waitingForPrep')
        return

    def setPlace(self, avId, totalTime, place, entryFee, qualify, winnings,
                 bonus, trophies, circuitPoints, circuitTime):
        if self.fsm.getCurrentState().getName() == 'leaving':
            return
        if avId == localAvatar.doId:
            cheerToPlay = place + (4 - self.numRacers)
            if cheerToPlay > 4:
                cheerToPlay = 4
            self.victory = base.loadSfx(self.SFX_Applause % cheerToPlay)
            self.victory.play()
        self.knownPlace[avId] = place
        kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
        avatar = base.cr.doId2do.get(avId, None)
        if avatar:
            self.gui.racerFinished(avId, self.trackId, place, totalTime,
                                   entryFee, qualify, winnings, bonus,
                                   trophies, circuitPoints, circuitTime)
            taskName = 'hideAv: %s' % avId
            taskMgr.doMethodLater(6,
                                  avatar.reparentTo,
                                  taskName,
                                  extraArgs=[hidden])
            self.miscTaskNames.append(taskName)
        if kart:
            taskName = 'hideKart: %s' % self.localKart.doId
            taskMgr.doMethodLater(6,
                                  kart.reparentTo,
                                  taskName,
                                  extraArgs=[hidden])
            self.miscTaskNames.append(taskName)
        return

    def setCircuitPlace(self, avId, place, entryFee, winnings, bonus,
                        trophies):
        print 'setting cicruit place'
        if self.fsm.getCurrentState().getName() == 'leaving':
            return
        if avId == localAvatar.doId:
            cheerToPlay = place + (4 - self.numRacers)
            self.victory = base.loadSfx(self.SFX_Applause % cheerToPlay)
            self.victory.play()
        oldPlace = 0
        if self.knownPlace.get(avId):
            oldPlace = self.knownPlace[avId]
            self.placeFixup.append([oldPlace - 1, place - 1])
        avatar = base.cr.doId2do.get(avId, None)
        if avatar:
            print 'circuit trophies %s' % trophies
            print 'winnings %s' % winnings
            self.gui.racerFinishedCircuit(avId, oldPlace, entryFee, winnings,
                                          bonus, trophies)
        return

    def endCircuitRace(self):
        print self.placeFixup
        self.gui.circuitFinished(self.placeFixup)

    def prepForRace(self):
        self.fsm.request('prep')

    def startRace(self, startTime=0):
        self.baseTime = globalClockDelta.networkToLocalTime(startTime)
        self.fsm.request('start')

    def startTutorial(self):
        self.fsm.request('tutorial')

    def genGag(self, slot, number, type):
        self.notify.debug('making gag...')
        if not self.gags[slot].isActive():
            self.gags[slot].genGag(number, type)

    def dropAnvilOn(self, ownerId, avId, timeStamp):
        kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
        if kart:
            if avId != ownerId:
                if avId == localAvatar.doId:
                    self.anvilFall.play()
                    kart.dropOnMe(timeStamp)
                else:
                    kart.dropOnHim(timeStamp)
        return

    def shootPiejectile(self, sourceId, targetId, type=0):
        kart = base.cr.doId2do.get(self.kartMap.get(sourceId, None), None)
        if kart:
            self.piejectileManager.addPiejectile(sourceId, targetId, type)
        return

    def goToSpeedway(self, avIds, reason=RaceGlobals.Exit_UserReq):
        self.notify.debug('goToSpeedway %s %s' % (avIds, reason))
        if localAvatar.doId in avIds:
            base.loader.endBulkLoad('atRace')
            self.kartCleanup()
            self.doneBarrier('waitingForExit')
            self.sendUpdate('racerLeft', [localAvatar.doId])
            out = {
                'loader': 'safeZoneLoader',
                'where': 'playground',
                'how': 'teleportIn',
                'hoodId': localAvatar.lastHood,
                'zoneId': localAvatar.lastHood,
                'shardId': None,
                'avId': -1,
                'reason': reason
            }
            base.cr.playGame.fsm.request('quietZone', [out])
        return

    def kartCleanup(self):
        kart = self.localKart
        if kart:
            kart.setState('P', 0)
            for i in self.avIds:
                if i != localAvatar.doId:
                    toon = base.cr.doId2do.get(i, None)
                    if toon:
                        toon.stopSmooth()
                        toon.setScale(1)
                        toon.setShear(0, 0, 0)
                        toon.reparentTo(render)
                        kart.doHeadScale(toon, None)

        localAvatar.setPos(0, 14, 0)
        localAvatar.sendCurrentPosition()
        return

    def heresMyT(self, avId, avNumLaps, avTime, timestamp):
        self.gui.updateRacerInfo(avId, curvetime=avNumLaps + avTime)

    def setZoneId(self, zoneId):
        self.zoneId = zoneId

    def setRaceType(self, raceType):
        self.raceType = raceType

    def setCircuitLoop(self, circuitLoop):
        self.circuitLoop = circuitLoop

    def setTrackId(self, id):
        DistributedRace.notify.debug('setTrackId: %s' % id)
        self.trackId = id

    def setAvatars(self, avIds):
        ids = ''
        for i in avIds:
            ids += str(i) + ' '

        DistributedRace.notify.debug('setAvatars: %s' % ids)
        self.avIds = avIds
        self.avT = [0] * len(self.avIds)

    def setLapCount(self, lapCount):
        self.lapCount = lapCount

    def setStartingPlaces(self, startList):
        self.startingPlaces = startList

    def enterJoin(self):
        self.doneBarrier('waitingForJoin')
        self.notify.debug('entering Join')

    def exitJoin(self):
        pass

    def setEnteredRacers(self, avAndKarts):
        self.notify.debug('setEnteredRacers %s' % avAndKarts)
        avatarsGone = []
        avatarsLeft = []
        self.numRacers = len(avAndKarts)
        for i in avAndKarts:
            if i[0] in self.avIds:
                self.kartMap[i[0]] = i[1]
                avatarsLeft.append(i[0])

        for i in self.avIds:
            if i not in avatarsLeft:
                avatarsGone.append(i)

        base.loader.tick()
        for i in avatarsGone:
            self.avIds.remove(i)

        self.toonsToLink = list(self.avIds)
        for i in avAndKarts:
            self.cr.relatedObjectMgr.requestObjects(
                i, allCallback=self.__gotKartAvatarLink)

    def __gotKartAvatarLink(self, avAndKart):
        self.notify.debug('got a Link')
        toon = avAndKart[0]
        kart = avAndKart[1]
        base.loader.tick()
        if toon.doId in self.toonsToLink:
            self.toonsToLink.remove(toon.doId)
        if toon.doId == localAvatar.doId:
            self.localKart = kart
        if len(self.toonsToLink) == 0:
            self.doneBarrier('waitingForPrep')

    def enterPrep(self):
        self.d_requestKart()
        self.notify.debug('entering Prep State')
        if self.reversed:
            self.spin = Vec3(180, 0, 0)
        else:
            self.spin = Vec3(0, 0, 0)
        for i in range(4):
            base.loader.tick()

        self.gui.initRaceMode()
        self.gui.initResultMode()
        self.myPos = self.startingPos[self.startingPlaces[self.avIds.index(
            localAvatar.doId)]]
        self.localKart.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        self.localKart.setupLapCollisions()
        if self.dummyNode:
            self.dummyNode.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        self.currentPole = self.findSegmentStart()
        self.rabbitPoint = Vec3(0, 0, 0)
        self.doneBarrier('waitingForReady')

    def exitPrep(self):
        pass

    def enterTutorial(self):
        self.notify.debug('entering Tutorial State')
        base.loader.endBulkLoad('atRace')
        self.localKart.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        base.transitions.irisIn()
        self.rulesDoneEvent = 'finishedRules'
        self.accept(self.rulesDoneEvent, self.handleRulesDone)
        self.rulesPanel = MinigameRulesPanel.MinigameRulesPanel(
            'RacingRulesPanel', self.getTitle(), self.getInstructions(),
            self.rulesDoneEvent, 10)
        self.rulesPanel.load()
        self.rulesPanel.frame.setPos(0, 0, -0.6667)
        self.rulesPanel.enter()

    def exitTutorial(self):
        self.ignore(self.rulesDoneEvent)
        self.rulesPanel.exit()
        self.rulesPanel.unload()
        del self.rulesPanel

    def getTitle(self):
        return TTLocalizer.KartRace_TitleInfo

    def getInstructions(self):
        return TTLocalizer.KartRace_TrackInfo[self.trackId]

    def handleRulesDone(self):
        self.doneBarrier('readRules')
        self.fsm.request('waiting')

    def enterWaiting(self):
        self.waitingLabel = DirectLabel()
        self.waitingLabel['text'] = TTLocalizer.BuildingWaitingForVictors
        self.waitingLabel.setScale(TTLocalizer.DRenterWaiting)

    def exitWaiting(self):
        self.waitingLabel.removeNode()

    def enterStart(self):
        waitTime = self.baseTime - globalClock.getFrameTime()
        taskName = 'enableRaceModeLater'
        taskMgr.doMethodLater(1,
                              self.gui.enableRaceMode,
                              taskName,
                              extraArgs=[])
        self.miscTaskNames.append(taskName)
        for i in self.avIds:
            self.gui.racerEntered(i)

        self.startCountdownClock(waitTime, 0)
        taskMgr.doMethodLater(waitTime,
                              self.fsm.request,
                              'goToRacing',
                              extraArgs=['racing'])

    def exitStart(self):
        pass

    def enterRacing(self):
        self.localKart.setInput(1)
        self.gui.setTimerEnabled(True)
        self.raceTask = taskMgr.add(self.raceWatcher, 'raceWatcher')

    def exitRacing(self):
        pass

    def raceWatcher(self, task):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None),
                                   None)
        if self.localKart.amIClampingPosition():
            self.notify.debug('teleporting kart %d back to main track' %
                              localAvatar.doId)
            self.localKart.setPos(self.curvePoints[self.currentPole])
        kartPoint = self.localKart.getPos()
        direction = 0
        while True:
            currPoint = self.curvePoints[self.currentPole]
            nextPole = (self.currentPole + 1) % len(self.curvePoints)
            nextPoint = self.curvePoints[nextPole]
            segment = nextPoint - currPoint
            segment.setZ(0)
            segLength2 = segment.lengthSquared()
            kartVector = kartPoint - currPoint
            kartVector.setZ(0)
            project = segment * (segment.dot(kartVector) / segLength2)
            projLength2 = project.lengthSquared()
            if project.dot(segment) < 0:
                if direction == 1:
                    break
                prevPole = (self.currentPole - 1) % len(self.curvePoints)
                self.currentPole = prevPole
                direction = -1
            elif projLength2 > segLength2:
                if direction == -1:
                    break
                self.currentPole = nextPole
                direction = 1
            else:
                break

        if self.dummyNode:
            self.dummyNode.setPos(kartPoint[0], kartPoint[1], 0)
            self.dummyNode.setHpr(self.localKart.getH(), 0, 0)
        t = projLength2 / segLength2
        if self.debugIt:
            self.notify.debug('self.debugIt = %d' % self.debugIt)
            import pdb
            pdb.set_trace()
        if nextPole < self.currentPole:
            newT = self.curveTs[self.currentPole] * (
                1 - t) + self.curve.getMaxT() * t
        else:
            newT = self.curveTs[self.currentPole] * (
                1 - t) + self.curveTs[nextPole] * t
        kartDirection = self.localKart.forward.getPos(
            render) - self.localKart.getPos(render)
        kartDirection.normalize()
        project.normalize()
        globalDirection = kartDirection.dot(project)
        if globalDirection < 0:
            self.wrongWay = True
        elif globalDirection > 0.1:
            self.wrongWay = False
        newLapT = (newT - self.startT) / self.curve.getMaxT() % 1.0
        if newLapT - self.currLapT < -0.5:
            self.laps += 1
            self.changeMusicTempo(1 + self.laps * 0.5)
            self.notify.debug('crossed the start line: %s, %s, %s, %s' %
                              (self.laps, self.startT, self.currT, newT))
        elif newLapT - self.currLapT > 0.5:
            self.laps -= 1
            self.changeMusicTempo(1 + self.laps * 0.5)
            self.notify.debug(
                'crossed the start line - wrong way: %s, %s, %s, %s' %
                (self.laps, self.startT, self.currT, newT))
        self.currT = newT
        self.currLapT = newLapT
        if self.isUrbanTrack:
            self.showBuildings(self.currT)
        now = globalClock.getFrameTime()
        timestamp = globalClockDelta.localToNetworkTime(now)
        if self.laps == self.lapCount:
            self.sendUpdate(
                'heresMyT',
                [localAvatar.doId, self.laps, self.currLapT, timestamp])
            self.fsm.request('finished')
        if self.laps > self.maxLap:
            self.maxLap = self.laps
            self.sendUpdate(
                'heresMyT',
                [localAvatar.doId, self.laps, self.currLapT, timestamp])
        if now - self.lastTimeUpdate > 0.5:
            self.lastTimeUpdate = now
            self.sendUpdate(
                'heresMyT',
                [localAvatar.doId, self.laps, self.currLapT, timestamp])
        self.gui.updateRacerInfo(localAvatar.doId,
                                 curvetime=self.currLapT + self.laps)
        self.gui.update(now)
        return Task.cont

    def enterFinished(self):
        taskMgr.remove('raceWatcher')
        self.fadeOutMusic()
        self.localKart.interruptTurbo()
        self.localKart.disableControls()
        taskName = 'parkIt'
        taskMgr.doMethodLater(2, self.stopDriving, taskName, extraArgs=[])
        self.miscTaskNames.append(taskName)
        self.finished = True
        camera.reparentTo(render)
        camera.setPos(self.localKart.getPos(render) + Vec3(0, 0, 10))
        camera.setH(self.localKart.getH(render) + 180)
        self.gui.disableRaceMode()
        self.gui.enableResultMode()
        localAvatar.reparentTo(hidden)
        self.localKart.reparentTo(hidden)

    def exitFinished(self):
        pass

    def stopDriving(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None),
                                   None)
        cpos = camera.getPos()
        chpr = camera.getHpr()
        localAvatar.reparentTo(hidden)
        self.localKart.reparentTo(hidden)
        self.localKart.stopSmooth()
        self.localKart.stopPosHprBroadcast()
        camera.setPos(cpos)
        camera.setHpr(chpr)
        return

    def enterLeave(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None),
                                   None)
        taskMgr.remove('raceWatcher')
        self.gui.disable()
        if self.localKart:
            self.localKart.disableControls()
        base.transitions.irisOut()
        if self.raceType == RaceGlobals.Circuit and not len(
                self.circuitLoop) == 0:
            self.sendUpdate('racerLeft', [localAvatar.doId])
        else:
            taskMgr.doMethodLater(1,
                                  self.goToSpeedway,
                                  'leaveRace',
                                  extraArgs=[[localAvatar.doId],
                                             RaceGlobals.Exit_UserReq])
        if self.victory:
            self.victory.stop()
        self.bananaSound.stop()
        self.anvilFall.stop()
        return

    def exitLeave(self):
        pass

    def getCountdownColor(self, countdownTimeInt):
        clockNodeColors = [
            Vec4(0, 1, 0, 1),
            Vec4(1, 1, 0, 1),
            Vec4(1, 0.5, 0, 1),
            Vec4(1, 0, 0, 1)
        ]
        i = max(min(countdownTimeInt, len(clockNodeColors) - 1), 0)
        return clockNodeColors[i]

    def startCountdownClock(self, countdownTime, ts):
        self.clockNode = TextNode('k')
        self.clockNode.setFont(ToontownGlobals.getSignFont())
        self.clockNode.setAlign(TextNode.ACenter)
        countdownInt = int(countdownTime)
        self.clockNode.setTextColor(self.getCountdownColor(countdownInt))
        self.clockNode.setText(str(countdownInt))
        self.clock = render2d.attachNewNode(self.clockNode)
        rs = TTLocalizer.DRrollScale
        self.clock.setPosHprScale(0, 0, 0, 0, 0, 0, rs, rs, rs)
        self.clock.hide()
        if ts < countdownTime:
            self.countdown(countdownTime - ts)

    def timerTask(self, task):
        countdownTime = int(task.duration - task.time)
        timeStr = str(countdownTime + 1)
        if self.clock.isHidden():
            if task.duration - task.time <= task.maxCount:
                self.clock.show()
        if self.clockNode.getText() != timeStr:
            self.startBoopSfx.play()
            self.clockNode.setText(timeStr)
            self.clockNode.setTextColor(
                self.getCountdownColor(countdownTime + 1))
        if task.time >= task.duration:
            self.startBoop2Sfx.play()
            self.clockNode.setText(TTLocalizer.KartRace_Go)
            self.clockNode.setTextColor(self.getCountdownColor(-1))
            taskMgr.doMethodLater(1, self.endGoSign, 'removeGoSign')
            return Task.done
        else:
            return Task.cont

    def endGoSign(self, t):
        self.clock.removeNode()

    def countdown(self, duration):
        countdownTask = Task(self.timerTask)
        countdownTask.duration = duration
        countdownTask.maxCount = RaceGlobals.RaceCountdown
        taskMgr.remove(self.uniqueName('countdownTimerTask'))
        return taskMgr.add(countdownTask,
                           self.uniqueName('countdownTimerTask'))

    def initGags(self):
        self.banana = globalPropPool.getProp('banana')
        self.banana.setScale(2)
        self.pie = globalPropPool.getProp('creampie')
        self.pie.setScale(1)

    def makeCheckPoint(self, trigger, location, event):
        cs = CollisionSphere(0, 0, 0, 140)
        cs.setTangible(0)
        triggerEvent = 'imIn-' + trigger
        cn = CollisionNode(trigger)
        cn.addSolid(cs)
        cn.setIntoCollideMask(BitMask32(32768))
        cn.setFromCollideMask(BitMask32(32768))
        cnp = NodePath(cn)
        cnp.reparentTo(self.geom)
        cnp.setPos(location)
        self.accept(triggerEvent, event)

    def loadUrbanTrack(self):
        dnaFile = 'phase_6/models/karting/urban_town_track.bam.pz'
        if self.trackId in (RaceGlobals.RT_Urban_2,
                            RaceGlobals.RT_Urban_2_rev):
            dnaFile = 'phase_6/models/karting/urban_town_track_B.bam.pz'
        self.townGeom = loader.loadModel(dnaFile)
        self.townGeom.reparentTo(self.geom)
        self.townGeom.findAllMatches('**/+CollisionNode').stash()
        self.buildingGroups = {}
        self.currBldgInd = {}
        self.currBldgGroups = {}
        bgGeom = self.geom.find('**/polySurface8')
        if self.dummyNode:
            bgGeom.reparentTo(self.dummyNode)
        else:
            bgGeom.reparentTo(localAvatar)
        bgGeom.setScale(0.1)
        ce = CompassEffect.make(NodePath(), CompassEffect.PRot)
        bgGeom.node().setEffect(ce)
        bgGeom.setDepthTest(0)
        bgGeom.setDepthWrite(0)
        bgGeom.setBin('background', 102)
        bgGeom.setZ(-1)
        self.bgGeom = bgGeom
        l = self.geom.findAllMatches('**/+ModelNode')
        for n in l:
            n.node().setPreserveTransform(0)
        self.geom.flattenLight()
        maxNum = 0
        for side in ['inner', 'outer']:
            self.buildingGroups[side] = []
            self.currBldgInd[side] = None
            self.currBldgGroups[side] = None
            i = 0
            while 1:
                bldgGroup = self.townGeom.find('**/Buildings_' + side + '-' +
                                               str(i))
                if bldgGroup.isEmpty():
                    break
                l = bldgGroup.findAllMatches('**/+ModelNode')
                for n in l:
                    n2 = n.getParent().attachNewNode(n.getName())
                    n.getChildren().reparentTo(n2)
                    n.removeNode()
                bldgGroup.flattenStrong()
                if not bldgGroup.getNode(0).getBounds().isEmpty():
                    self.buildingGroups[side].append(bldgGroup)
                i += 1
            if i > maxNum:
                maxNum = i
        for side in ['innersidest', 'outersidest']:
            self.buildingGroups[side] = []
            self.currBldgInd[side] = None
            self.currBldgGroups[side] = None
            for i in range(maxNum):
                for barricade in ('innerbarricade', 'outerbarricade'):
                    bldgGroup = self.townGeom.find('**/Buildings_' + side +
                                                   '-' + barricade + '_' +
                                                   str(i))
                    if bldgGroup.isEmpty():
                        continue
                    l = bldgGroup.findAllMatches('**/+ModelNode')
                    for n in l:
                        n2 = n.getParent().attachNewNode(n.getName())
                        n.getChildren().reparentTo(n2)
                        n.removeNode()
                    self.buildingGroups[side].append(bldgGroup)
        treeNodes = self.townGeom.findAllMatches('**/prop_tree_*')
        for tree in treeNodes:
            tree.flattenStrong()
        snowTreeNodes = self.townGeom.findAllMatches('**/prop_snow_tree_*')
        for snowTree in snowTreeNodes:
            snowTree.flattenStrong()
        for side in ['inner', 'outer', 'innersidest', 'outersidest']:
            for grp in self.buildingGroups[side]:
                grp.stash()
        self.showBuildings(0)

    def unloadUrbanTrack(self):
        del self.buildingGroups
        self.townGeom.removeNode()

    def loadFog(self):
        self.hasFog = True
        if self.isUrbanTrack:
            base.camLens.setFar(650)
        else:
            base.camLens.setFar(650)
        self.dummyNode = render.attachNewNode('dummyNode')
        if base.wantFog:
            self.fog = Fog('TrackFog')
            self.fog.setColor(Vec4(0.6, 0.7, 0.8, 1.0))
            if self.isUrbanTrack:
                self.fog.setLinearRange(200.0, 650.0)
            else:
                self.fog.setLinearRange(200.0, 800.0)
            render.setFog(self.fog)
        self.sky.setScale(1.725)
        self.sky.reparentTo(self.dummyNode)

    def showBuildings(self, t, forceRecompute=False):
        firstTimeCalled = 0
        if self.curve:
            t = t / self.curve.getMaxT()
        else:
            firstTimeCalled = 1
        if self.reversed:
            t = 1.0 - t
        numGroupsShown = 5
        for side in ['inner', 'outer']:
            numBldgGroups = len(self.buildingGroups[side])
            bldgInd = int(t * numBldgGroups)
            bldgInd = bldgInd % numBldgGroups
            if self.trackId in (RaceGlobals.RT_Urban_2,
                                RaceGlobals.RT_Urban_2_rev):
                oldBldgInd = int(self.oldT * numBldgGroups)
                newBldgInd = int(t * numBldgGroups)
                kartPoint = self.startPos
                kart = base.cr.doId2do.get(
                    self.kartMap.get(localAvatar.doId, None), None)
                if kart:
                    kartPoint = self.localKart.getPos()
                if not self.currBldgInd[side]:
                    self.currBldgInd[side] = 0
                curInd = self.currBldgInd[side]
                myCurGroup = self.buildingGroups[side][curInd]
                prevGrp = (curInd - 1) % numBldgGroups
                myPrevGroup = self.buildingGroups[side][prevGrp]
                nextGrp = (curInd + 1) % numBldgGroups
                myNextGroup = self.buildingGroups[side][nextGrp]
                curVector = myCurGroup.getNode(
                    0).getBounds().getCenter() - kartPoint
                curDistance = curVector.lengthSquared()
                prevVector = myPrevGroup.getNode(
                    0).getBounds().getCenter() - kartPoint
                prevDistance = prevVector.lengthSquared()
                nextVector = myNextGroup.getNode(
                    0).getBounds().getCenter() - kartPoint
                nextDistance = nextVector.lengthSquared()
                if curDistance <= prevDistance and curDistance <= nextDistance:
                    bldgInd = self.currBldgInd[side]
                elif prevDistance <= curDistance and prevDistance <= nextDistance:
                    bldgInd = prevGrp
                elif nextDistance <= curDistance and nextDistance <= prevDistance:
                    bldgInd = nextGrp
                else:
                    self.notify.warning('unhandled case!!!!')
                    bldgInd = self.currBldgInd[side]
            if bldgInd != self.currBldgInd[side]:
                currBldgGroups = self.currBldgGroups[side]
                if currBldgGroups:
                    for i in currBldgGroups:
                        self.buildingGroups[side][i].stash()

                prevGrp2 = (bldgInd - 2) % numBldgGroups
                prevGrp = (bldgInd - 1) % numBldgGroups
                currGrp = bldgInd % numBldgGroups
                nextGrp = (bldgInd + 1) % numBldgGroups
                nextGrp2 = (bldgInd + 2) % numBldgGroups
                self.currBldgGroups[side] = [
                    prevGrp2, prevGrp, currGrp, nextGrp, nextGrp2
                ]
                for i in self.currBldgGroups[side]:
                    self.buildingGroups[side][i].unstash()

                self.currBldgInd[side] = bldgInd

        if self.currBldgGroups['inner'] != self.currBldgGroups['outer']:
            pass
        if t != self.oldT:
            self.oldT = t
        if self.trackId in (RaceGlobals.RT_Urban_2,
                            RaceGlobals.RT_Urban_2_rev):
            if self.reversed:
                t = 1.0 - t
            for side in ['innersidest', 'outersidest']:
                segmentInd = int(t * self.barricadeSegments)
                seglmentInd = segmentInd % self.barricadeSegments
                if segmentInd != self.currBldgInd[side] or forceRecompute:
                    currBldgGroups = self.currBldgGroups[side]
                    if currBldgGroups:
                        for i in currBldgGroups:
                            self.buildingGroups[side][i].stash()

                    self.currBldgGroups[side] = []
                    if side == 'innersidest':
                        dict = self.innerBarricadeDict
                    elif side == 'outersidest':
                        dict = self.outerBarricadeDict
                    if dict.has_key(segmentInd):
                        self.currBldgGroups[side] = dict[segmentInd]
                    for i in self.currBldgGroups[side]:
                        self.buildingGroups[side][i].unstash()

                    self.currBldgInd[side] = segmentInd

        return

    def setupGeom(self):
        trackFilepath = RaceGlobals.TrackDict[self.trackId][0]
        self.geom = loader.loadModel(trackFilepath)
        for i in range(10):
            base.loader.tick()

        self.geom.reparentTo(render)
        if self.reversed:
            lapStartPos = self.geom.find('**/lap_start_rev').getPos()
        else:
            lapStartPos = self.geom.find('**/lap_start').getPos()
        self.startPos = lapStartPos
        lapMidPos = self.geom.find('**/lap_middle').getPos()
        for i in range(5):
            base.loader.tick()

        self.startingPos = []
        posLocators = self.geom.findAllMatches('**/start_pos*')
        for i in range(posLocators.getNumPaths()):
            base.loader.tick()
            self.startingPos.append(
                [posLocators[i].getPos(), posLocators[i].getHpr()])

        self.notify.debug('self.startingPos: %s' % self.startingPos)
        self.wrongWay = False
        self.laps = 0
        if self.isUrbanTrack:
            self.loadUrbanTrack()
        self.genArrows()
        if self.reversed:
            self.curve = self.geom.find('**/curve_reverse').node()
        else:
            self.curve = self.geom.find('**/curve_forward').node()
        for i in range(4000):
            self.curvePoints.append(Point3(0, 0, 0))
            self.curve.getPoint(i / 4000.0 * (self.curve.getMaxT() - 1e-11),
                                self.curvePoints[-1])
            self.curveTs.append(i / 4000.0 * (self.curve.getMaxT() - 1e-11))

        if self.trackId in (RaceGlobals.RT_Urban_2,
                            RaceGlobals.RT_Urban_2_rev):
            self.precomputeSideStreets()
        for i in range(10):
            base.loader.tick()

        self.startT = self.getNearestT(lapStartPos)
        self.midT = self.getNearestT(lapMidPos)
        self.gags = []
        gagList = RaceGlobals.TrackDict[self.trackId][4]
        for i in range(len(gagList)):
            self.notify.debug('generating gag: %s' % i)
            self.gags.append(
                RaceGag(self, i,
                        Vec3(*gagList[i]) + Vec3(0, 0, 3)))

        for i in range(5):
            base.loader.tick()

    def precomputeSideStreets(self):
        farDist = base.camLens.getFar() + 300
        farDistSquared = farDist * farDist
        for i in range(int(self.barricadeSegments)):
            testPoint = Point3(0, 0, 0)
            self.curve.getPoint(
                i / self.barricadeSegments * (self.curve.getMaxT() - 1e-11),
                testPoint)
            for side in ('innersidest', 'outersidest'):
                for bldgGroupIndex in range(len(self.buildingGroups[side])):
                    bldgGroup = self.buildingGroups[side][bldgGroupIndex]
                    if not bldgGroup.getNode(0).getBounds().isEmpty():
                        bldgPoint = bldgGroup.getNode(
                            0).getBounds().getCenter()
                        vector = testPoint - bldgPoint
                        if vector.lengthSquared() < farDistSquared:
                            if side == 'innersidest':
                                dict = self.innerBarricadeDict
                            elif side == 'outersidest':
                                dict = self.outerBarricadeDict
                            else:
                                self.notify.error('unhandled side')
                            if dict.has_key(i):
                                if bldgGroupIndex not in dict[i]:
                                    dict[i].append(bldgGroupIndex)
                            else:
                                dict[i] = [bldgGroupIndex]
                    for childIndex in (0, ):
                        if childIndex >= bldgGroup.getNumChildren():
                            continue
                        childNodePath = bldgGroup.getChild(childIndex)
                        bldgPoint = childNodePath.node().getBounds().getCenter(
                        )
                        vector = testPoint - bldgPoint
                        if vector.lengthSquared() < farDistSquared:
                            if side == 'innersidest':
                                dict = self.innerBarricadeDict
                            elif side == 'outersidest':
                                dict = self.outerBarricadeDict
                            else:
                                self.notify.error('unhandled side')
                            if dict.has_key(i):
                                if bldgGroupIndex not in dict[i]:
                                    dict[i].append(bldgGroupIndex)
                            else:
                                dict[i] = [bldgGroupIndex]

        for side in ('innersidest', 'outersidest'):
            for bldgGroup in self.buildingGroups[side]:
                bldgGroup.flattenStrong()

        if self.isUrbanTrack:
            self.showBuildings(0, forceRecompute=True)

    def findSegmentStart(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None),
                                   None)
        minLength2 = 1000000
        minIndex = -1
        currPoint = Point3(0, 0, 0)
        kartPoint = self.localKart.getPos()
        for i in range(len(self.curvePoints)):
            currPoint = self.curvePoints[i]
            currLength2 = (kartPoint - currPoint).lengthSquared()
            if currLength2 < minLength2:
                minLength2 = currLength2
                minIndex = i

        currPoint = self.curvePoints[minIndex]
        if minIndex + 1 == len(self.curvePoints):
            nextPoint = self.curvePoints[0]
        else:
            nextPoint = self.curvePoints[minIndex + 1]
        if minIndex - 1 < 0:
            prevIndex = len(self.curvePoints) - 1
        else:
            prevIndex = minIndex - 1
        forwardSegment = nextPoint - currPoint
        if (kartPoint - currPoint).dot(forwardSegment) > 0:
            return minIndex
        else:
            return prevIndex
        return

    def getNearestT(self, pos):
        minLength2 = 1000000
        minIndex = -1
        currPoint = Point3(0, 0, 0)
        for i in range(len(self.curvePoints)):
            currPoint = self.curvePoints[i]
            currLength2 = (pos - currPoint).lengthSquared()
            if currLength2 < minLength2:
                minLength2 = currLength2
                minIndex = i

        currPoint = self.curvePoints[minIndex]
        if minIndex + 1 == len(self.curvePoints):
            nextPoint = self.curvePoints[0]
        else:
            nextPoint = self.curvePoints[minIndex + 1]
        if minIndex - 1 < 0:
            prevIndex = len(self.curvePoints) - 1
        else:
            prevIndex = minIndex - 1
        forwardSegment = nextPoint - currPoint
        if (pos - currPoint).dot(forwardSegment) > 0:
            pole = minIndex
        else:
            pole = prevIndex
        currPoint = self.curvePoints[pole]
        nextPole = (pole + 1) % len(self.curvePoints)
        nextPoint = self.curvePoints[nextPole]
        segment = nextPoint - currPoint
        segment.setZ(0)
        segLength2 = segment.lengthSquared()
        posVector = pos - currPoint
        posVector.setZ(0)
        project = segment * (segment.dot(posVector) / segLength2)
        percent = project.lengthSquared() / segLength2
        if nextPole < pole:
            t = self.curveTs[pole] * (1 -
                                      percent) + self.curve.getMaxT() * percent
        else:
            t = self.curveTs[pole] * (
                1 - percent) + self.curveTs[nextPole] * percent
        return t

    def hasGag(self, slot, type, index):
        if self.gags[slot].isActive():
            self.gags[slot].disableGag()

    def leaveRace(self):
        self.fsm.request('leave')

    def racerLeft(self, avId):
        if avId != localAvatar.doId:
            self.gui.racerLeft(avId, unexpected=False)

    def skyTrack(self, task):
        return SkyUtil.cloudSkyTrack(task)

    def startSky(self):
        if self.hasFog:
            SkyUtil.startCloudSky(self,
                                  parent=self.dummyNode,
                                  effects=CompassEffect.PRot)
        else:
            SkyUtil.startCloudSky(self, parent=render)

    def stopSky(self):
        taskMgr.remove('skyTrack')

    def pickupGag(self, slot, index):
        self.canShoot = False
        standing = self.gui.racerDict[localAvatar.doId].place - 1
        self.currGag = RaceGlobals.GagFreq[standing][index]
        cycleTime = 2
        self.gui.waitingOnGag(cycleTime)
        taskMgr.doMethodLater(cycleTime, self.enableShoot, 'enableShoot')
        self.sendUpdate('hasGag', [slot, self.currGag, index])

    def shootGag(self):
        if self.canShoot:
            if self.currGag == 1:
                self.bananaSound.play()
                self.shootBanana()
            elif self.currGag == 2:
                self.d_requestThrow(0, 0, 0)
                self.localKart.startTurbo()
            elif self.currGag == 3:
                self.d_requestThrow(0, 0, 0)
            elif self.currGag == 4:
                self.bananaSound.play()
                self.shootPie()
            self.currGag = 0
            self.gui.updateGag(0)

    def enableShoot(self, t):
        self.canShoot = True
        if self.gui:
            self.gui.updateGag(self.currGag)

    def shootBanana(self):
        pos = self.localKart.getPos(render)
        banana = self.banana.copyTo(self.geom)
        banana.setPos(pos)
        self.thrownGags.append(banana)
        self.d_requestThrow(pos[0], pos[1], pos[2])

    def shootPie(self):
        pos = self.localKart.getPos(render)
        self.d_requestThrow(pos[0], pos[1], pos[2])

    def genArrows(self):
        base.arrows = []
        arrowId = 0
        for boost in RaceGlobals.TrackDict[self.trackId][5]:
            self.genArrow(boost[0], boost[1], arrowId)
            arrowId += 1

    def genArrow(self, pos, hpr, id):
        factory = CardMaker('factory')
        factory.setFrame(-.5, 0.5, -.5, 0.5)
        arrowNode = factory.generate()
        arrowRoot = NodePath('root')
        baseArrow = NodePath(arrowNode)
        baseArrow.setTransparency(1)
        baseArrow.setTexture(self.boostArrowTexture)
        baseArrow.reparentTo(arrowRoot)
        arrow2 = baseArrow.copyTo(baseArrow)
        arrow2.setPos(0, 0, 1)
        arrow3 = arrow2.copyTo(arrow2)
        arrowRoot.setPos(*pos)
        arrowRoot.setHpr(*hpr)
        baseArrow.setHpr(0, -90, 0)
        baseArrow.setScale(24)
        arrowRoot.reparentTo(self.geom)
        trigger = 'boostArrow' + str(id)
        cs = CollisionTube(Point3(0.6, -6, 0), Point3(0.6, 54, 0), 4.8)
        cs.setTangible(0)
        triggerEvent = 'imIn-' + trigger
        cn = CollisionNode(trigger)
        cn.addSolid(cs)
        cn.setIntoCollideMask(BitMask32(32768))
        cn.setFromCollideMask(BitMask32(32768))
        cnp = NodePath(cn)
        cnp.reparentTo(arrowRoot)
        self.accept(triggerEvent, self.hitBoostArrow)
        arrowVec = arrow2.getPos(self.geom) - baseArrow.getPos(self.geom)
        arrowVec.normalize()
        idStr = str(id)
        cnp.setTag('boostId', idStr)
        self.boostDir[idStr] = arrowVec
        base.arrows.append(arrowRoot)

    def hitBoostArrow(self, cevent):
        into = cevent.getIntoNodePath()
        idStr = into.getTag('boostId')
        arrowVec = self.boostDir.get(idStr)
        if arrowVec == None:
            print 'Unknown boost arrow %s' % idStr
            return
        fvec = self.localKart.forward.getPos(
            self.geom) - self.localKart.getPos(self.geom)
        fvec.normalize()
        dotP = arrowVec.dot(fvec)
        if dotP > 0.7:
            self.localKart.startTurbo()
        return

    def fadeOutMusic(self):
        if self.musicTrack:
            self.musicTrack.finish()
        curVol = self.raceMusic.getVolume()
        interval = LerpFunctionInterval(self.raceMusic.setVolume,
                                        fromData=curVol,
                                        toData=0,
                                        duration=3)
        self.musicTrack = Sequence(interval)
        self.musicTrack.start()

    def changeMusicTempo(self, newPR):
        return  # TODO: Reenable when we have music change support.
        if self.musicTrack:
            self.musicTrack.finish()
        curPR = self.raceMusic.getPlayRate()
        interval = LerpFunctionInterval(self.raceMusic.setPlayRate,
                                        fromData=curPR,
                                        toData=newPR,
                                        duration=3)
        self.musicTrack = Sequence(interval)
        self.musicTrack.start()

    def setRaceZone(self, zoneId, trackId):
        hoodId = self.cr.playGame.hood.hoodId
        base.loader.endBulkLoad('atRace')
        self.kartCleanup()
        self.doneBarrier('waitingForExit')
        self.sendUpdate('racerLeft', [localAvatar.doId])
        out = {
            'loader': 'racetrack',
            'where': 'racetrack',
            'hoodId': hoodId,
            'zoneId': zoneId,
            'trackId': trackId,
            'shardId': None,
            'reason': RaceGlobals.Exit_UserReq
        }
        base.cr.playGame.hood.loader.fsm.request('quietZone', [out])
        return
class DistributedRace(DistributedObject.DistributedObject):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRace')
    ReadyPost = 'RaceReady'
    WinEvent = 'RaceWinEvent'
    BGM_BaseDir = 'phase_6/audio/bgm/'
    SFX_BaseDir = 'phase_6/audio/sfx/'
    SFX_StartBoop = SFX_BaseDir + 'KART_raceStart1.ogg'
    SFX_StartBoop2 = SFX_BaseDir + 'KART_raceStart2.ogg'
    SFX_Applause = SFX_BaseDir + 'KART_Applause_%d.ogg'

    def __init__(self, cr):
        self.qbox = loader.loadModel('phase_6/models/karting/qbox')
        self.boostArrowTexture = loader.loadTexture('phase_6/maps/boost_arrow.jpg', 'phase_6/maps/boost_arrow_a.rgb')
        self.boostArrowTexture.setMinfilter(Texture.FTLinear)
        DistributedObject.DistributedObject.__init__(self, cr)
        self.kartMap = {}
        self.fsm = ClassicFSM.ClassicFSM('Race', [State.State('join', self.enterJoin, self.exitJoin, ['prep', 'leave']),
         State.State('prep', self.enterPrep, self.exitPrep, ['tutorial', 'leave']),
         State.State('tutorial', self.enterTutorial, self.exitTutorial, ['start', 'waiting', 'leave']),
         State.State('waiting', self.enterWaiting, self.exitWaiting, ['start', 'leave']),
         State.State('start', self.enterStart, self.exitStart, ['racing', 'leave']),
         State.State('racing', self.enterRacing, self.exitRacing, ['finished', 'leave']),
         State.State('finished', self.enterFinished, self.exitFinished, ['leave']),
         State.State('leave', self.enterLeave, self.exitLeave, [])], 'join', 'leave')
        self.gui = RaceGUI(self)
        base.race = self
        self.currT = 0
        self.currLapT = 0
        self.currGag = 0
        self.tdelay = 0
        self.finished = False
        self.thrownGags = []
        self.effectManager = EffectManager.EffectManager()
        self.piejectileManager = PiejectileManager.PiejectileManager()
        self.lastTimeUpdate = globalClock.getFrameTime()
        self.initGags()
        self.canShoot = True
        self.isUrbanTrack = False
        self.hasFog = False
        self.dummyNode = None
        self.fog = None
        self.bananaSound = base.loadSfx('phase_6/audio/sfx/KART_tossBanana.ogg')
        self.anvilFall = base.loadSfx('phase_6/audio/sfx/KART_Gag_Hit_Anvil.ogg')
        self.accept('leaveRace', self.leaveRace)
        self.toonsToLink = []
        self.curveTs = []
        self.curvePoints = []
        self.localKart = None
        self.musicTrack = None
        self.victory = None
        self.miscTaskNames = []
        self.boostDir = {}
        self.knownPlace = {}
        self.placeFixup = []
        self.curve = None
        self.barricadeSegments = 100.0
        self.outerBarricadeDict = {}
        self.innerBarricadeDict = {}
        self.maxLap = 0
        self.oldT = 0
        self.debugIt = 0
        self.startPos = None
        return

    def generate(self):
        self.notify.debug('generate: %s' % self.doId)
        DistributedObject.DistributedObject.generate(self)
        bboard.post('race', self)
        self.roomWatcher = None
        self.cutoff = 0.01
        self.startBoopSfx = base.loadSfx(self.SFX_StartBoop)
        self.startBoop2Sfx = base.loadSfx(self.SFX_StartBoop2)
        return

    def announceGenerate(self):
        self.notify.debug('announceGenerate: %s' % self.doId)
        DistributedObject.DistributedObject.announceGenerate(self)
        musicFile = self.BGM_BaseDir + RaceGlobals.TrackDict[self.trackId][7]
        self.raceMusic = base.loadMusic(musicFile)
        base.playMusic(self.raceMusic, looping=1, volume=0.8)
        base.camera.reparentTo(render)
        if self.trackId in (RaceGlobals.RT_Urban_1,
         RaceGlobals.RT_Urban_1_rev,
         RaceGlobals.RT_Urban_2,
         RaceGlobals.RT_Urban_2_rev):
            self.isUrbanTrack = True
        self.oldFarPlane = base.camLens.getFar()
        base.camLens.setFar(12000)
        localAvatar.startPosHprBroadcast()
        localAvatar.d_broadcastPositionNow()
        DistributedSmoothNode.activateSmoothing(1, 1)
        self.reversed = self.trackId / 2.0 > int(self.trackId / 2.0)
        for i in xrange(3):
            base.loader.tick()

        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.sky.setPos(0, 0, 0)
        self.sky.setScale(20.0)
        self.sky.setFogOff()
        if self.trackId in (RaceGlobals.RT_Urban_1,
         RaceGlobals.RT_Urban_1_rev,
         RaceGlobals.RT_Urban_2,
         RaceGlobals.RT_Urban_2_rev):
            self.loadFog()
        self.setupGeom()
        self.startSky()
        for i in xrange(5):
            base.loader.tick()

    def disable(self):
        self.notify.debug('disable %s' % self.doId)
        if self.musicTrack:
            self.musicTrack.finish()
        self.raceMusic.stop()
        self.stopSky()
        if self.sky is not None:
            self.sky.removeNode()
        if self.dummyNode:
            self.dummyNode.removeNode()
            self.dummyNode = None
        for taskName in self.miscTaskNames:
            taskMgr.remove(taskName)

        taskMgr.remove('raceWatcher')
        self.ignoreAll()
        DistributedSmoothNode.activateSmoothing(1, 0)
        if self.isUrbanTrack:
            self.unloadUrbanTrack()
        if self.fog:
            render.setFogOff()
            del self.fog
            self.fog = None
        if self.geom is not None:
            self.geom.hide()
        base.camLens.setFar(self.oldFarPlane)
        DistributedObject.DistributedObject.disable(self)
        return

    def delete(self):
        self.notify.debug('delete %s' % self.doId)
        if self.gui:
            self.gui.destroy()
            self.gui = None
        if self.geom is not None:
            self.geom.removeNode()
            self.geom = None
            for i in self.gags:
                i.delete()
                del i

        self.piejectileManager.delete()
        if self.curveTs:
            del self.curveTs
        if self.curvePoints:
            del self.curvePoints
        if self.curve:
            del self.curve
        if self.victory:
            del self.victory
        del self.fsm
        del self.anvilFall
        del self.bananaSound
        del self.localKart
        DistributedObject.DistributedObject.delete(self)
        taskMgr.remove(self.uniqueName('countdownTimerTask'))
        taskMgr.remove('raceWatcher')
        bboard.remove('race')
        self.ignoreAll()
        del base.race
        return

    def d_requestThrow(self, x, y, z):
        self.sendUpdate('requestThrow', [x, y, z])

    def d_requestKart(self):
        self.sendUpdate('requestKart', [])

    def waitingForJoin(self):
        self.notify.debug('I got the barrier')
        self.fsm.enterInitialState()

    def racerDisconnected(self, avId):
        self.notify.debug('lost racer: %s' % avId)
        if avId in self.kartMap:
            if avId in self.toonsToLink:
                self.toonsToLink.remove(avId)
            toon = base.cr.doId2do.get(avId, None)
            kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
            self.avIds.remove(avId)
            del self.kartMap[avId]
            self.gui.racerLeft(avId, unexpected=True)
            if kart:
                kart.reparentTo(hidden)
            if toon:
                toon.reparentTo(hidden)
            if len(self.toonsToLink) == 0:
                self.doneBarrier('waitingForPrep')
        return

    def setPlace(self, avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime):
        if self.fsm.getCurrentState().getName() == 'leaving':
            return
        if avId == localAvatar.doId:
            cheerToPlay = place + (4 - self.numRacers)
            if cheerToPlay > 4:
                cheerToPlay = 4
            self.victory = base.loadSfx(self.SFX_Applause % cheerToPlay)
            self.victory.play()
        self.knownPlace[avId] = place
        kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
        avatar = base.cr.doId2do.get(avId, None)
        if avatar:
            self.gui.racerFinished(avId, self.trackId, place, totalTime, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime)
            taskName = 'hideAv: %s' % avId
            taskMgr.doMethodLater(6, avatar.reparentTo, taskName, extraArgs=[hidden])
            self.miscTaskNames.append(taskName)
        if kart:
            taskName = 'hideKart: %s' % self.localKart.doId
            taskMgr.doMethodLater(6, kart.reparentTo, taskName, extraArgs=[hidden])
            self.miscTaskNames.append(taskName)
        return

    def setCircuitPlace(self, avId, place, entryFee, winnings, bonus, trophies):
        print 'setting cicruit place'
        if self.fsm.getCurrentState().getName() == 'leaving':
            return
        if avId == localAvatar.doId:
            cheerToPlay = place + (4 - self.numRacers)
            self.victory = base.loadSfx(self.SFX_Applause % cheerToPlay)
            self.victory.play()
        oldPlace = 0
        if self.knownPlace.get(avId):
            oldPlace = self.knownPlace[avId]
            self.placeFixup.append([oldPlace - 1, place - 1])
        avatar = base.cr.doId2do.get(avId, None)
        if avatar:
            print 'circuit trophies %s' % trophies
            print 'winnings %s' % winnings
            self.gui.racerFinishedCircuit(avId, oldPlace, entryFee, winnings, bonus, trophies)
        return

    def endCircuitRace(self):
        print self.placeFixup
        self.gui.circuitFinished(self.placeFixup)

    def prepForRace(self):
        self.fsm.request('prep')

    def startRace(self, startTime = 0):
        self.baseTime = globalClockDelta.networkToLocalTime(startTime)
        self.fsm.request('start')

    def startTutorial(self):
        self.fsm.request('tutorial')

    def genGag(self, slot, number, type):
        self.notify.debug('making gag...')
        if not self.gags[slot].isActive():
            self.gags[slot].genGag(number, type)

    def dropAnvilOn(self, ownerId, avId, timeStamp):
        kart = base.cr.doId2do.get(self.kartMap.get(avId, None), None)
        if kart:
            if avId != ownerId:
                if avId == localAvatar.doId:
                    self.anvilFall.play()
                    kart.dropOnMe(timeStamp)
                else:
                    kart.dropOnHim(timeStamp)
        return

    def shootPiejectile(self, sourceId, targetId, type = 0):
        kart = base.cr.doId2do.get(self.kartMap.get(sourceId, None), None)
        if kart:
            self.piejectileManager.addPiejectile(sourceId, targetId, type)
        return

    def goToSpeedway(self, avIds, reason = RaceGlobals.Exit_UserReq):
        self.notify.debug('goToSpeedway %s %s' % (avIds, reason))
        if localAvatar.doId in avIds:
            base.loader.endBulkLoad('atRace')
            self.kartCleanup()
            self.doneBarrier('waitingForExit')
            self.sendUpdate('racerLeft', [localAvatar.doId])
            out = {'loader': 'safeZoneLoader',
             'where': 'playground',
             'how': 'teleportIn',
             'hoodId': localAvatar.lastHood,
             'zoneId': localAvatar.lastHood,
             'shardId': None,
             'avId': -1,
             'reason': reason}
            base.cr.playGame.fsm.request('quietZone', [out])
        return

    def kartCleanup(self):
        kart = self.localKart
        if kart:
            kart.setState('P', 0)
            for i in self.avIds:
                if i != localAvatar.doId:
                    toon = base.cr.doId2do.get(i, None)
                    if toon:
                        toon.stopSmooth()
                        toon.setScale(1)
                        toon.setShear(0, 0, 0)
                        toon.reparentTo(render)
                        kart.doHeadScale(toon, None)

        localAvatar.setPos(0, 14, 0)
        localAvatar.sendCurrentPosition()
        return

    def heresMyT(self, avId, avNumLaps, avTime, timestamp):
        self.gui.updateRacerInfo(avId, curvetime=avNumLaps + avTime)

    def setZoneId(self, zoneId):
        self.zoneId = zoneId

    def setRaceType(self, raceType):
        self.raceType = raceType

    def setCircuitLoop(self, circuitLoop):
        self.circuitLoop = circuitLoop

    def setTrackId(self, id):
        DistributedRace.notify.debug('setTrackId: %s' % id)
        self.trackId = id

    def setAvatars(self, avIds):
        ids = ''
        for i in avIds:
            ids += str(i) + ' '

        DistributedRace.notify.debug('setAvatars: %s' % ids)
        self.avIds = avIds
        self.avT = [0] * len(self.avIds)

    def setLapCount(self, lapCount):
        self.lapCount = lapCount

    def setStartingPlaces(self, startList):
        self.startingPlaces = startList

    def enterJoin(self):
        self.doneBarrier('waitingForJoin')
        self.notify.debug('entering Join')

    def exitJoin(self):
        pass

    def setEnteredRacers(self, avAndKarts):
        self.notify.debug('setEnteredRacers %s' % avAndKarts)
        avatarsGone = []
        avatarsLeft = []
        self.numRacers = len(avAndKarts)
        for i in avAndKarts:
            if i[0] in self.avIds:
                self.kartMap[i[0]] = i[1]
                avatarsLeft.append(i[0])

        for i in self.avIds:
            if i not in avatarsLeft:
                avatarsGone.append(i)

        base.loader.tick()
        for i in avatarsGone:
            self.avIds.remove(i)

        self.toonsToLink = list(self.avIds)
        for i in avAndKarts:
            self.cr.relatedObjectMgr.requestObjects(i, allCallback=self.__gotKartAvatarLink)

    def __gotKartAvatarLink(self, avAndKart):
        self.notify.debug('got a Link')
        toon = avAndKart[0]
        kart = avAndKart[1]
        base.loader.tick()
        if toon.doId in self.toonsToLink:
            self.toonsToLink.remove(toon.doId)
        if toon.doId == localAvatar.doId:
            self.localKart = kart
        if len(self.toonsToLink) == 0:
            self.doneBarrier('waitingForPrep')

    def enterPrep(self):
        self.d_requestKart()
        self.notify.debug('entering Prep State')
        if self.reversed:
            self.spin = Vec3(180, 0, 0)
        else:
            self.spin = Vec3(0, 0, 0)
        for i in xrange(4):
            base.loader.tick()

        self.gui.initRaceMode()
        self.gui.initResultMode()
        self.myPos = self.startingPos[self.startingPlaces[self.avIds.index(localAvatar.doId)]]
        self.localKart.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        self.localKart.setupLapCollisions()
        if self.dummyNode:
            self.dummyNode.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        self.currentPole = self.findSegmentStart()
        self.rabbitPoint = Vec3(0, 0, 0)
        self.doneBarrier('waitingForReady')

    def exitPrep(self):
        pass

    def enterTutorial(self):
        self.notify.debug('entering Tutorial State')
        base.loader.endBulkLoad('atRace')
        self.localKart.setPosHpr(self.myPos[0], self.myPos[1] + self.spin)
        base.transitions.irisIn()
        self.rulesDoneEvent = 'finishedRules'
        self.accept(self.rulesDoneEvent, self.handleRulesDone)
        self.rulesPanel = MinigameRulesPanel.MinigameRulesPanel('RacingRulesPanel', self.getTitle(), self.getInstructions(), self.rulesDoneEvent, 10)
        self.rulesPanel.load()
        self.rulesPanel.frame.setPos(0, 0, -0.6667)
        self.rulesPanel.enter()

    def exitTutorial(self):
        self.ignore(self.rulesDoneEvent)
        self.rulesPanel.exit()
        self.rulesPanel.unload()
        del self.rulesPanel

    def getTitle(self):
        return TTLocalizer.KartRace_TitleInfo

    def getInstructions(self):
        return TTLocalizer.KartRace_TrackInfo[self.trackId]

    def handleRulesDone(self):
        self.doneBarrier('readRules')
        self.fsm.request('waiting')

    def enterWaiting(self):
        self.waitingLabel = DirectLabel()
        self.waitingLabel['text'] = TTLocalizer.BuildingWaitingForVictors
        self.waitingLabel.setScale(TTLocalizer.DRenterWaiting)

    def exitWaiting(self):
        self.waitingLabel.removeNode()

    def enterStart(self):
        waitTime = self.baseTime - globalClock.getFrameTime()
        taskName = 'enableRaceModeLater'
        taskMgr.doMethodLater(1, self.gui.enableRaceMode, taskName, extraArgs=[])
        self.miscTaskNames.append(taskName)
        for i in self.avIds:
            self.gui.racerEntered(i)

        self.startCountdownClock(waitTime, 0)
        taskMgr.doMethodLater(waitTime, self.fsm.request, 'goToRacing', extraArgs=['racing'])

    def exitStart(self):
        pass

    def enterRacing(self):
        self.localKart.setInput(1)
        self.gui.setTimerEnabled(True)
        self.raceTask = taskMgr.add(self.raceWatcher, 'raceWatcher')

    def exitRacing(self):
        pass

    def raceWatcher(self, task):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None), None)
        if self.localKart.amIClampingPosition():
            self.notify.debug('teleporting kart %d back to main track' % localAvatar.doId)
            self.localKart.setPos(self.curvePoints[self.currentPole])
        kartPoint = self.localKart.getPos()
        direction = 0
        while True:
            currPoint = self.curvePoints[self.currentPole]
            nextPole = (self.currentPole + 1) % len(self.curvePoints)
            nextPoint = self.curvePoints[nextPole]
            segment = nextPoint - currPoint
            segment.setZ(0)
            segLength2 = segment.lengthSquared()
            kartVector = kartPoint - currPoint
            kartVector.setZ(0)
            project = segment * (segment.dot(kartVector) / segLength2)
            projLength2 = project.lengthSquared()
            if project.dot(segment) < 0:
                if direction == 1:
                    break
                prevPole = (self.currentPole - 1) % len(self.curvePoints)
                self.currentPole = prevPole
                direction = -1
            elif projLength2 > segLength2:
                if direction == -1:
                    break
                self.currentPole = nextPole
                direction = 1
            else:
                break

        if self.dummyNode:
            self.dummyNode.setPos(kartPoint[0], kartPoint[1], 0)
            self.dummyNode.setHpr(self.localKart.getH(), 0, 0)
        t = projLength2 / segLength2
        if self.debugIt:
            self.notify.debug('self.debugIt = %d' % self.debugIt)
            import pdb
            pdb.set_trace()
        if nextPole < self.currentPole:
            newT = self.curveTs[self.currentPole] * (1 - t) + self.curve.getMaxT() * t
        else:
            newT = self.curveTs[self.currentPole] * (1 - t) + self.curveTs[nextPole] * t
        kartDirection = self.localKart.forward.getPos(render) - self.localKart.getPos(render)
        kartDirection.normalize()
        project.normalize()
        globalDirection = kartDirection.dot(project)
        if globalDirection < 0:
            self.wrongWay = True
        elif globalDirection > 0.1:
            self.wrongWay = False
        newLapT = (newT - self.startT) / self.curve.getMaxT() % 1.0
        if newLapT - self.currLapT < -0.5:
            self.laps += 1
            self.changeMusicTempo(1 + self.laps * 0.5)
            self.notify.debug('crossed the start line: %s, %s, %s, %s' % (self.laps,
             self.startT,
             self.currT,
             newT))
        elif newLapT - self.currLapT > 0.5:
            self.laps -= 1
            self.changeMusicTempo(1 + self.laps * 0.5)
            self.notify.debug('crossed the start line - wrong way: %s, %s, %s, %s' % (self.laps,
             self.startT,
             self.currT,
             newT))
        self.currT = newT
        self.currLapT = newLapT
        if self.isUrbanTrack:
            self.showBuildings(self.currT)
        now = globalClock.getFrameTime()
        timestamp = globalClockDelta.localToNetworkTime(now)
        if self.laps == self.lapCount:
            self.sendUpdate('heresMyT', [localAvatar.doId,
             self.laps,
             self.currLapT,
             timestamp])
            self.fsm.request('finished')
        if self.laps > self.maxLap:
            self.maxLap = self.laps
            self.sendUpdate('heresMyT', [localAvatar.doId,
             self.laps,
             self.currLapT,
             timestamp])
        if now - self.lastTimeUpdate > 0.5:
            self.lastTimeUpdate = now
            self.sendUpdate('heresMyT', [localAvatar.doId,
             self.laps,
             self.currLapT,
             timestamp])
        self.gui.updateRacerInfo(localAvatar.doId, curvetime=self.currLapT + self.laps)
        self.gui.update(now)
        return Task.cont

    def enterFinished(self):
        taskMgr.remove('raceWatcher')
        self.fadeOutMusic()
        self.localKart.interruptTurbo()
        self.localKart.disableControls()
        taskName = 'parkIt'
        taskMgr.doMethodLater(2, self.stopDriving, taskName, extraArgs=[])
        self.miscTaskNames.append(taskName)
        self.finished = True
        base.camera.reparentTo(render)
        base.camera.setPos(self.localKart.getPos(render) + Vec3(0, 0, 10))
        base.camera.setH(self.localKart.getH(render) + 180)
        self.gui.disableRaceMode()
        self.gui.enableResultMode()
        localAvatar.reparentTo(hidden)
        self.localKart.reparentTo(hidden)

    def exitFinished(self):
        pass

    def stopDriving(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None), None)
        cpos = base.camera.getPos()
        chpr = base.camera.getHpr()
        localAvatar.reparentTo(hidden)
        self.localKart.reparentTo(hidden)
        self.localKart.stopSmooth()
        self.localKart.stopPosHprBroadcast()
        base.camera.setPos(cpos)
        base.camera.setHpr(chpr)
        return

    def enterLeave(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None), None)
        taskMgr.remove('raceWatcher')
        self.gui.disable()
        if self.localKart:
            self.localKart.disableControls()
        base.transitions.irisOut()
        if self.raceType == RaceGlobals.Circuit and not len(self.circuitLoop) == 0:
            self.sendUpdate('racerLeft', [localAvatar.doId])
        else:
            taskMgr.doMethodLater(1, self.goToSpeedway, 'leaveRace', extraArgs=[[localAvatar.doId], RaceGlobals.Exit_UserReq])
        if self.victory:
            self.victory.stop()
        self.bananaSound.stop()
        self.anvilFall.stop()
        return

    def exitLeave(self):
        pass

    def getCountdownColor(self, countdownTimeInt):
        clockNodeColors = [Vec4(0, 1, 0, 1),
         Vec4(1, 1, 0, 1),
         Vec4(1, 0.5, 0, 1),
         Vec4(1, 0, 0, 1)]
        i = max(min(countdownTimeInt, len(clockNodeColors) - 1), 0)
        return clockNodeColors[i]

    def startCountdownClock(self, countdownTime, ts):
        self.clockNode = TextNode('k')
        self.clockNode.setFont(ToontownGlobals.getSignFont())
        self.clockNode.setAlign(TextNode.ACenter)
        countdownInt = int(countdownTime)
        self.clockNode.setTextColor(self.getCountdownColor(countdownInt))
        self.clockNode.setText(str(countdownInt))
        self.clock = render2d.attachNewNode(self.clockNode)
        rs = TTLocalizer.DRrollScale
        self.clock.setPosHprScale(0, 0, 0, 0, 0, 0, rs, rs, rs)
        self.clock.hide()
        if ts < countdownTime:
            self.countdown(countdownTime - ts)

    def timerTask(self, task):
        countdownTime = int(task.duration - task.time)
        timeStr = str(countdownTime + 1)
        if self.clock.isHidden():
            if task.duration - task.time <= task.maxCount:
                self.clock.show()
        if self.clockNode.getText() != timeStr:
            self.startBoopSfx.play()
            self.clockNode.setText(timeStr)
            self.clockNode.setTextColor(self.getCountdownColor(countdownTime + 1))
        if task.time >= task.duration:
            self.startBoop2Sfx.play()
            self.clockNode.setText(TTLocalizer.KartRace_Go)
            self.clockNode.setTextColor(self.getCountdownColor(-1))
            taskMgr.doMethodLater(1, self.endGoSign, 'removeGoSign')
            return Task.done
        else:
            return Task.cont

    def endGoSign(self, t):
        self.clock.removeNode()

    def countdown(self, duration):
        countdownTask = Task(self.timerTask)
        countdownTask.duration = duration
        countdownTask.maxCount = RaceGlobals.RaceCountdown
        taskMgr.remove(self.uniqueName('countdownTimerTask'))
        return taskMgr.add(countdownTask, self.uniqueName('countdownTimerTask'))

    def initGags(self):
        self.banana = globalPropPool.getProp('banana')
        self.banana.setScale(2)
        self.pie = globalPropPool.getProp('creampie')
        self.pie.setScale(1)

    def makeCheckPoint(self, trigger, location, event):
        cs = CollisionSphere(0, 0, 0, 140)
        cs.setTangible(0)
        triggerEvent = 'imIn-' + trigger
        cn = CollisionNode(trigger)
        cn.addSolid(cs)
        cn.setIntoCollideMask(BitMask32(32768))
        cn.setFromCollideMask(BitMask32(32768))
        cnp = NodePath(cn)
        cnp.reparentTo(self.geom)
        cnp.setPos(location)
        self.accept(triggerEvent, event)

    def loadUrbanTrack(self):
        self.dnaStore = DNAStorage()
        files = ('phase_4/dna/storage.pdna', 'phase_5/dna/storage_town.pdna',
                 'phase_4/dna/storage_TT.pdna', 'phase_5/dna/storage_TT_town.pdna',
                 'phase_8/dna/storage_BR.pdna', 'phase_8/dna/storage_BR_town.pdna',
                 'phase_8/dna/storage_DL.pdna', 'phase_8/dna/storage_DL_town.pdna')
        dnaBulk = DNABulkLoader(self.dnaStore, files)
        dnaBulk.loadDNAFiles()
        dnaFile = 'phase_6/dna/urban_track_town.pdna'
        if self.trackId in (RaceGlobals.RT_Urban_2, RaceGlobals.RT_Urban_2_rev):
            dnaFile = 'phase_6/dna/urban_track_town_B.pdna'
        node = loader.loadDNAFile(self.dnaStore, dnaFile)
        self.townGeom = self.geom.attachNewNode(node)
        self.townGeom.findAllMatches('**/+CollisionNode').stash()
        self.buildingGroups = {}
        self.currBldgInd = {}
        self.currBldgGroups = {}
        bgGeom = self.geom.find('**/polySurface8')
        if self.dummyNode:
            bgGeom.reparentTo(self.dummyNode)
        else:
            bgGeom.reparentTo(localAvatar)
        bgGeom.setScale(0.1)
        ce = CompassEffect.make(NodePath(), CompassEffect.PRot)
        bgGeom.node().setEffect(ce)
        bgGeom.setDepthTest(0)
        bgGeom.setDepthWrite(0)
        bgGeom.setBin('background', 102)
        bgGeom.setZ(-1)
        self.bgGeom = bgGeom
        l = self.geom.findAllMatches('**/+ModelNode')
        for n in l:
            n.node().setPreserveTransform(0)
        self.geom.flattenLight()
        maxNum = 0
        for side in ['inner', 'outer']:
            self.buildingGroups[side] = []
            self.currBldgInd[side] = None
            self.currBldgGroups[side] = None
            i = 0
            while 1:
                bldgGroup = self.townGeom.find('**/Buildings_' + side + '-' + str(i))
                if bldgGroup.isEmpty():
                    break
                l = bldgGroup.findAllMatches('**/+ModelNode')
                for n in l:
                    n2 = n.getParent().attachNewNode(n.getName())
                    n.getChildren().reparentTo(n2)
                    n.removeNode()
                bldgGroup.flattenStrong()
                if not bldgGroup.getNode(0).getBounds().isEmpty():
                    self.buildingGroups[side].append(bldgGroup)
                i += 1
            if i > maxNum:
                maxNum = i
        for side in ['innersidest', 'outersidest']:
            self.buildingGroups[side] = []
            self.currBldgInd[side] = None
            self.currBldgGroups[side] = None
            for i in xrange(maxNum):
                for barricade in ('innerbarricade', 'outerbarricade'):
                    bldgGroup = self.townGeom.find('**/Buildings_' + side + '-' + barricade + '_' + str(i))
                    if bldgGroup.isEmpty():
                        continue
                    l = bldgGroup.findAllMatches('**/+ModelNode')
                    for n in l:
                        n2 = n.getParent().attachNewNode(n.getName())
                        n.getChildren().reparentTo(n2)
                        n.removeNode()
                    self.buildingGroups[side].append(bldgGroup)
        treeNodes = self.townGeom.findAllMatches('**/prop_tree_*')
        for tree in treeNodes:
            tree.flattenStrong()
        snowTreeNodes = self.townGeom.findAllMatches('**/prop_snow_tree_*')
        for snowTree in snowTreeNodes:
            snowTree.flattenStrong()
        for side in ['inner', 'outer', 'innersidest', 'outersidest']:
            for grp in self.buildingGroups[side]:
                grp.stash()
        self.showBuildings(0)

    def unloadUrbanTrack(self):
        del self.buildingGroups
        self.townGeom.removeNode()

    def loadFog(self):
        self.hasFog = True
        if self.isUrbanTrack:
            base.camLens.setFar(650)
        else:
            base.camLens.setFar(650)
        self.dummyNode = render.attachNewNode('dummyNode')
        if base.wantFog:
            self.fog = Fog('TrackFog')
            self.fog.setColor(Vec4(0.6, 0.7, 0.8, 1.0))
            if self.isUrbanTrack:
                self.fog.setLinearRange(200.0, 650.0)
            else:
                self.fog.setLinearRange(200.0, 800.0)
            render.setFog(self.fog)
        self.sky.setScale(1.725)
        self.sky.reparentTo(self.dummyNode)

    def showBuildings(self, t, forceRecompute = False):
        firstTimeCalled = 0
        if self.curve:
            t = t / self.curve.getMaxT()
        else:
            firstTimeCalled = 1
        if self.reversed:
            t = 1.0 - t
        numGroupsShown = 5
        for side in ['inner', 'outer']:
            numBldgGroups = len(self.buildingGroups[side])
            bldgInd = int(t * numBldgGroups)
            bldgInd = bldgInd % numBldgGroups
            if self.trackId in (RaceGlobals.RT_Urban_2, RaceGlobals.RT_Urban_2_rev):
                oldBldgInd = int(self.oldT * numBldgGroups)
                newBldgInd = int(t * numBldgGroups)
                kartPoint = self.startPos
                kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None), None)
                if kart:
                    kartPoint = self.localKart.getPos()
                if not self.currBldgInd[side]:
                    self.currBldgInd[side] = 0
                curInd = self.currBldgInd[side]
                myCurGroup = self.buildingGroups[side][curInd]
                prevGrp = (curInd - 1) % numBldgGroups
                myPrevGroup = self.buildingGroups[side][prevGrp]
                nextGrp = (curInd + 1) % numBldgGroups
                myNextGroup = self.buildingGroups[side][nextGrp]
                curVector = myCurGroup.getNode(0).getBounds().getCenter() - kartPoint
                curDistance = curVector.lengthSquared()
                prevVector = myPrevGroup.getNode(0).getBounds().getCenter() - kartPoint
                prevDistance = prevVector.lengthSquared()
                nextVector = myNextGroup.getNode(0).getBounds().getCenter() - kartPoint
                nextDistance = nextVector.lengthSquared()
                if curDistance <= prevDistance and curDistance <= nextDistance:
                    bldgInd = self.currBldgInd[side]
                elif prevDistance <= curDistance and prevDistance <= nextDistance:
                    bldgInd = prevGrp
                elif nextDistance <= curDistance and nextDistance <= prevDistance:
                    bldgInd = nextGrp
                else:
                    self.notify.warning('unhandled case!!!!')
                    bldgInd = self.currBldgInd[side]
            if bldgInd != self.currBldgInd[side]:
                currBldgGroups = self.currBldgGroups[side]
                if currBldgGroups:
                    for i in currBldgGroups:
                        self.buildingGroups[side][i].stash()

                prevGrp2 = (bldgInd - 2) % numBldgGroups
                prevGrp = (bldgInd - 1) % numBldgGroups
                currGrp = bldgInd % numBldgGroups
                nextGrp = (bldgInd + 1) % numBldgGroups
                nextGrp2 = (bldgInd + 2) % numBldgGroups
                self.currBldgGroups[side] = [prevGrp2,
                 prevGrp,
                 currGrp,
                 nextGrp,
                 nextGrp2]
                for i in self.currBldgGroups[side]:
                    self.buildingGroups[side][i].unstash()

                self.currBldgInd[side] = bldgInd

        if self.currBldgGroups['inner'] != self.currBldgGroups['outer']:
            pass
        if t != self.oldT:
            self.oldT = t
        if self.trackId in (RaceGlobals.RT_Urban_2, RaceGlobals.RT_Urban_2_rev):
            if self.reversed:
                t = 1.0 - t
            for side in ['innersidest', 'outersidest']:
                segmentInd = int(t * self.barricadeSegments)
                seglmentInd = segmentInd % self.barricadeSegments
                if segmentInd != self.currBldgInd[side] or forceRecompute:
                    currBldgGroups = self.currBldgGroups[side]
                    if currBldgGroups:
                        for i in currBldgGroups:
                            self.buildingGroups[side][i].stash()

                    self.currBldgGroups[side] = []
                    if side == 'innersidest':
                        dict = self.innerBarricadeDict
                    elif side == 'outersidest':
                        dict = self.outerBarricadeDict
                    if segmentInd in dict:
                        self.currBldgGroups[side] = dict[segmentInd]
                    for i in self.currBldgGroups[side]:
                        self.buildingGroups[side][i].unstash()

                    self.currBldgInd[side] = segmentInd

        return

    def setupGeom(self):
        trackFilepath = RaceGlobals.TrackDict[self.trackId][0]
        self.geom = loader.loadModel(trackFilepath)
        for i in xrange(10):
            base.loader.tick()

        self.geom.reparentTo(render)
        if self.reversed:
            lapStartPos = self.geom.find('**/lap_start_rev').getPos()
        else:
            lapStartPos = self.geom.find('**/lap_start').getPos()
        self.startPos = lapStartPos
        lapMidPos = self.geom.find('**/lap_middle').getPos()
        for i in xrange(5):
            base.loader.tick()

        self.startingPos = []
        posLocators = self.geom.findAllMatches('**/start_pos*')
        for i in xrange(posLocators.getNumPaths()):
            base.loader.tick()
            self.startingPos.append([posLocators[i].getPos(), posLocators[i].getHpr()])

        self.notify.debug('self.startingPos: %s' % self.startingPos)
        self.wrongWay = False
        self.laps = 0
        if self.isUrbanTrack:
            self.loadUrbanTrack()
        self.genArrows()
        if self.reversed:
            self.curve = self.geom.find('**/curve_reverse').node()
        else:
            self.curve = self.geom.find('**/curve_forward').node()
        for i in xrange(4000):
            self.curvePoints.append(Point3(0, 0, 0))
            self.curve.getPoint(i / 4000.0 * (self.curve.getMaxT() - 1e-11), self.curvePoints[-1])
            self.curveTs.append(i / 4000.0 * (self.curve.getMaxT() - 1e-11))

        if self.trackId in (RaceGlobals.RT_Urban_2, RaceGlobals.RT_Urban_2_rev):
            self.precomputeSideStreets()
        for i in xrange(10):
            base.loader.tick()

        self.startT = self.getNearestT(lapStartPos)
        self.midT = self.getNearestT(lapMidPos)
        self.gags = []
        gagList = RaceGlobals.TrackDict[self.trackId][4]
        for i in xrange(len(gagList)):
            self.notify.debug('generating gag: %s' % i)
            self.gags.append(RaceGag(self, i, Vec3(*gagList[i]) + Vec3(0, 0, 3)))

        for i in xrange(5):
            base.loader.tick()

    def precomputeSideStreets(self):
        farDist = base.camLens.getFar() + 300
        farDistSquared = farDist * farDist
        for i in xrange(int(self.barricadeSegments)):
            testPoint = Point3(0, 0, 0)
            self.curve.getPoint(i / self.barricadeSegments * (self.curve.getMaxT() - 1e-11), testPoint)
            for side in ('innersidest', 'outersidest'):
                for bldgGroupIndex in xrange(len(self.buildingGroups[side])):
                    bldgGroup = self.buildingGroups[side][bldgGroupIndex]
                    if not bldgGroup.getNode(0).getBounds().isEmpty():
                        bldgPoint = bldgGroup.getNode(0).getBounds().getCenter()
                        vector = testPoint - bldgPoint
                        if vector.lengthSquared() < farDistSquared:
                            if side == 'innersidest':
                                dict = self.innerBarricadeDict
                            elif side == 'outersidest':
                                dict = self.outerBarricadeDict
                            else:
                                self.notify.error('unhandled side')
                            if i in dict:
                                if bldgGroupIndex not in dict[i]:
                                    dict[i].append(bldgGroupIndex)
                            else:
                                dict[i] = [bldgGroupIndex]
                    for childIndex in (0,):
                        if childIndex >= bldgGroup.getNumChildren():
                            continue
                        childNodePath = bldgGroup.getChild(childIndex)
                        bldgPoint = childNodePath.node().getBounds().getCenter()
                        vector = testPoint - bldgPoint
                        if vector.lengthSquared() < farDistSquared:
                            if side == 'innersidest':
                                dict = self.innerBarricadeDict
                            elif side == 'outersidest':
                                dict = self.outerBarricadeDict
                            else:
                                self.notify.error('unhandled side')
                            if i in dict:
                                if bldgGroupIndex not in dict[i]:
                                    dict[i].append(bldgGroupIndex)
                            else:
                                dict[i] = [bldgGroupIndex]

        for side in ('innersidest', 'outersidest'):
            for bldgGroup in self.buildingGroups[side]:
                bldgGroup.flattenStrong()

        if self.isUrbanTrack:
            self.showBuildings(0, forceRecompute=True)

    def findSegmentStart(self):
        kart = base.cr.doId2do.get(self.kartMap.get(localAvatar.doId, None), None)
        minLength2 = 1000000
        minIndex = -1
        currPoint = Point3(0, 0, 0)
        kartPoint = self.localKart.getPos()
        for i in xrange(len(self.curvePoints)):
            currPoint = self.curvePoints[i]
            currLength2 = (kartPoint - currPoint).lengthSquared()
            if currLength2 < minLength2:
                minLength2 = currLength2
                minIndex = i

        currPoint = self.curvePoints[minIndex]
        if minIndex + 1 == len(self.curvePoints):
            nextPoint = self.curvePoints[0]
        else:
            nextPoint = self.curvePoints[minIndex + 1]
        if minIndex - 1 < 0:
            prevIndex = len(self.curvePoints) - 1
        else:
            prevIndex = minIndex - 1
        forwardSegment = nextPoint - currPoint
        if (kartPoint - currPoint).dot(forwardSegment) > 0:
            return minIndex
        else:
            return prevIndex
        return

    def getNearestT(self, pos):
        minLength2 = 1000000
        minIndex = -1
        currPoint = Point3(0, 0, 0)
        for i in xrange(len(self.curvePoints)):
            currPoint = self.curvePoints[i]
            currLength2 = (pos - currPoint).lengthSquared()
            if currLength2 < minLength2:
                minLength2 = currLength2
                minIndex = i

        currPoint = self.curvePoints[minIndex]
        if minIndex + 1 == len(self.curvePoints):
            nextPoint = self.curvePoints[0]
        else:
            nextPoint = self.curvePoints[minIndex + 1]
        if minIndex - 1 < 0:
            prevIndex = len(self.curvePoints) - 1
        else:
            prevIndex = minIndex - 1
        forwardSegment = nextPoint - currPoint
        if (pos - currPoint).dot(forwardSegment) > 0:
            pole = minIndex
        else:
            pole = prevIndex
        currPoint = self.curvePoints[pole]
        nextPole = (pole + 1) % len(self.curvePoints)
        nextPoint = self.curvePoints[nextPole]
        segment = nextPoint - currPoint
        segment.setZ(0)
        segLength2 = segment.lengthSquared()
        posVector = pos - currPoint
        posVector.setZ(0)
        project = segment * (segment.dot(posVector) / segLength2)
        percent = project.lengthSquared() / segLength2
        if nextPole < pole:
            t = self.curveTs[pole] * (1 - percent) + self.curve.getMaxT() * percent
        else:
            t = self.curveTs[pole] * (1 - percent) + self.curveTs[nextPole] * percent
        return t

    def hasGag(self, slot, type, index):
        if self.gags[slot].isActive():
            self.gags[slot].disableGag()

    def leaveRace(self):
        self.fsm.request('leave')

    def racerLeft(self, avId):
        if avId != localAvatar.doId:
            self.gui.racerLeft(avId, unexpected=False)

    def skyTrack(self, task):
        return SkyUtil.cloudSkyTrack(task)

    def startSky(self):
        if self.hasFog:
            SkyUtil.startCloudSky(self, parent=self.dummyNode, effects=CompassEffect.PRot)
        else:
            SkyUtil.startCloudSky(self, parent=render)

    def stopSky(self):
        taskMgr.remove('skyTrack')

    def pickupGag(self, slot, index):
        self.canShoot = False
        standing = self.gui.racerDict[localAvatar.doId].place - 1
        self.currGag = RaceGlobals.GagFreq[standing][index]
        cycleTime = 2
        self.gui.waitingOnGag(cycleTime)
        taskMgr.doMethodLater(cycleTime, self.enableShoot, 'enableShoot')
        self.sendUpdate('hasGag', [slot, self.currGag, index])

    def shootGag(self):
        if self.canShoot:
            if self.currGag == 1:
                self.bananaSound.play()
                self.shootBanana()
            elif self.currGag == 2:
                self.d_requestThrow(0, 0, 0)
                self.localKart.startTurbo()
            elif self.currGag == 3:
                self.d_requestThrow(0, 0, 0)
            elif self.currGag == 4:
                self.bananaSound.play()
                self.shootPie()
            self.currGag = 0
            self.gui.updateGag(0)

    def enableShoot(self, t):
        self.canShoot = True
        if self.gui:
            self.gui.updateGag(self.currGag)

    def shootBanana(self):
        pos = self.localKart.getPos(render)
        banana = self.banana.copyTo(self.geom)
        banana.setPos(pos)
        self.thrownGags.append(banana)
        self.d_requestThrow(pos[0], pos[1], pos[2])

    def shootPie(self):
        pos = self.localKart.getPos(render)
        self.d_requestThrow(pos[0], pos[1], pos[2])

    def genArrows(self):
        base.arrows = []
        arrowId = 0
        for boost in RaceGlobals.TrackDict[self.trackId][5]:
            self.genArrow(boost[0], boost[1], arrowId)
            arrowId += 1

    def genArrow(self, pos, hpr, id):
        factory = CardMaker('factory')
        factory.setFrame(-.5, 0.5, -.5, 0.5)
        arrowNode = factory.generate()
        arrowRoot = NodePath('root')
        baseArrow = NodePath(arrowNode)
        baseArrow.setTransparency(1)
        baseArrow.setTexture(self.boostArrowTexture)
        baseArrow.reparentTo(arrowRoot)
        arrow2 = baseArrow.copyTo(baseArrow)
        arrow2.setPos(0, 0, 1)
        arrow3 = arrow2.copyTo(arrow2)
        arrowRoot.setPos(*pos)
        arrowRoot.setHpr(*hpr)
        baseArrow.setHpr(0, -90, 0)
        baseArrow.setScale(24)
        arrowRoot.reparentTo(self.geom)
        trigger = 'boostArrow' + str(id)
        cs = CollisionTube(Point3(0.6, -6, 0), Point3(0.6, 54, 0), 4.8)
        cs.setTangible(0)
        triggerEvent = 'imIn-' + trigger
        cn = CollisionNode(trigger)
        cn.addSolid(cs)
        cn.setIntoCollideMask(BitMask32(32768))
        cn.setFromCollideMask(BitMask32(32768))
        cnp = NodePath(cn)
        cnp.reparentTo(arrowRoot)
        self.accept(triggerEvent, self.hitBoostArrow)
        arrowVec = arrow2.getPos(self.geom) - baseArrow.getPos(self.geom)
        arrowVec.normalize()
        idStr = str(id)
        cnp.setTag('boostId', idStr)
        self.boostDir[idStr] = arrowVec
        base.arrows.append(arrowRoot)

    def hitBoostArrow(self, cevent):
        into = cevent.getIntoNodePath()
        idStr = into.getTag('boostId')
        arrowVec = self.boostDir.get(idStr)
        if arrowVec == None:
            print 'Unknown boost arrow %s' % idStr
            return
        fvec = self.localKart.forward.getPos(self.geom) - self.localKart.getPos(self.geom)
        fvec.normalize()
        dotP = arrowVec.dot(fvec)
        if dotP > 0.7:
            self.localKart.startTurbo()
        return

    def fadeOutMusic(self):
        if self.musicTrack:
            self.musicTrack.finish()
        curVol = self.raceMusic.getVolume()
        interval = LerpFunctionInterval(self.raceMusic.setVolume, fromData=curVol, toData=0, duration=3)
        self.musicTrack = Sequence(interval)
        self.musicTrack.start()

    def changeMusicTempo(self, newPR):
        return # TODO: Reenable when we have music change support.
        if self.musicTrack:
            self.musicTrack.finish()
        curPR = self.raceMusic.getPlayRate()
        interval = LerpFunctionInterval(self.raceMusic.setPlayRate, fromData=curPR, toData=newPR, duration=3)
        self.musicTrack = Sequence(interval)
        self.musicTrack.start()

    def setRaceZone(self, zoneId, trackId):
        hoodId = self.cr.playGame.hood.hoodId
        base.loader.endBulkLoad('atRace')
        self.kartCleanup()
        self.doneBarrier('waitingForExit')
        self.sendUpdate('racerLeft', [localAvatar.doId])
        out = {'loader': 'racetrack',
         'where': 'racetrack',
         'hoodId': hoodId,
         'zoneId': zoneId,
         'trackId': trackId,
         'shardId': None,
         'reason': RaceGlobals.Exit_UserReq}
        base.cr.playGame.hood.loader.fsm.request('quietZone', [out])
        return