text: from panda3d.core import *
  from toontown.toonbase.ToonBaseGlobal import *
  from direct.distributed.ClockDelta import *
  from DistributedMinigame import *
  from direct.gui.DirectGui import *
  from panda3d.core import *
  from direct.interval.IntervalGlobal import *
  from direct.fsm import ClassicFSM, State
  from direct.fsm import State
  from direct.task.Task import Task
  from toontown.toonbase import ToontownTimer
  import RaceGameGlobals
  from toontown.toonbase import ToontownGlobals
  from toontown.toonbase import TTLocalizer
  
  class DistributedRaceGame(DistributedMinigame):
  
      def __init__(self, cr):
          DistributedMinigame.__init__(self, cr)
          self.gameFSM = ClassicFSM.ClassicFSM('DistributedRaceGame', [State.State('off', self.enterOff, self.exitOff, ['inputChoice']),
           State.State('inputChoice', self.enterInputChoice, self.exitInputChoice, ['waitServerChoices', 'moveAvatars', 'cleanup']),
           State.State('waitServerChoices', self.enterWaitServerChoices, self.exitWaitServerChoices, ['moveAvatars', 'cleanup']),
           State.State('moveAvatars', self.enterMoveAvatars, self.exitMoveAvatars, ['inputChoice', 'winMovie', 'cleanup']),
           State.State('winMovie', self.enterWinMovie, self.exitWinMovie, ['cleanup']),
           State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
          self.addChildGameFSM(self.gameFSM)
          self.posHprArray = (
           (
            (-9.03, 0.06, 0.025, -152.9),
            (-7.43, -2.76, 0.025, -152.68),
            (-6.02, -5.48, 0.025, -157.54),
            (-5.01, -8.32, 0.025, -160.66),
            (-4.05, -11.36, 0.025, -170.22),
            (-3.49, -14.18, 0.025, -175.76),
            (-3.12, -17.15, 0.025, -177.73),
            (-3.0, -20.32, 0.025, 178.49),
            (-3.09, -23.44, 0.025, 176.59),
            (-3.43, -26.54, 0.025, 171.44),
            (-4.07, -29.44, 0.025, 163.75),
            (-5.09, -32.27, 0.025, 158.2),
            (-6.11, -35.16, 0.025, 154.98),
            (-7.57, -37.78, 0.025, 154.98),
            (-9.28, -40.65, 0.025, 150.41)),
           (
            (-6.12, 1.62, 0.025, -152.9),
            (-4.38, -1.35, 0.025, -150.92),
            (-3.08, -4.3, 0.025, -157.9),
            (-1.85, -7.26, 0.025, -162.54),
            (-0.93, -10.49, 0.025, -167.71),
            (-0.21, -13.71, 0.025, -171.79),
            (0.21, -17.08, 0.025, -174.92),
            (0.31, -20.2, 0.025, 177.1),
            (0.17, -23.66, 0.025, 174.82),
            (-0.23, -26.91, 0.025, 170.51),
            (-0.99, -30.2, 0.025, 162.54),
            (-2.02, -33.28, 0.025, 160.48),
            (-3.28, -36.38, 0.025, 157.96),
            (-4.67, -39.17, 0.025, 154.13),
            (-6.31, -42.15, 0.025, 154.13)),
           (
            (-2.99, 3.09, 0.025, -154.37),
            (-1.38, -0.05, 0.025, -154.75),
            (-0.19, -3.29, 0.025, -159.22),
            (1.17, -6.51, 0.025, -162.74),
            (2.28, -9.8, 0.025, -168.73),
            (3.09, -13.28, 0.025, -173.49),
            (3.46, -16.63, 0.025, -176.81),
            (3.69, -20.38, 0.025, 179.14),
            (3.61, -24.12, 0.025, 175.78),
            (3.0, -27.55, 0.025, 170.87),
            (2.15, -30.72, 0.025, 167.41),
            (1.04, -34.26, 0.025, 162.11),
            (-0.15, -37.44, 0.025, 158.59),
            (-1.64, -40.52, 0.025, 153.89),
            (-3.42, -43.63, 0.025, 153.89)),
           (
            (0.0, 4.35, 0.025, -154.37),
            (1.52, 1.3, 0.025, -155.67),
            (3.17, -2.07, 0.025, -155.67),
            (4.47, -5.41, 0.025, -163.0),
            (5.56, -9.19, 0.025, -168.89),
            (6.22, -12.66, 0.025, -171.67),
            (6.67, -16.56, 0.025, -176.53),
            (6.93, -20.33, 0.025, 179.87),
            (6.81, -24.32, 0.025, 175.19),
            (6.22, -27.97, 0.025, 170.81),
            (5.59, -31.73, 0.025, 167.54),
            (4.48, -35.42, 0.025, 161.92),
            (3.06, -38.82, 0.025, 158.56),
            (1.4, -42.0, 0.025, 154.32),
            (-0.71, -45.17, 0.025, 153.27)))
          self.avatarPositions = {}
          self.modelCount = 8
          self.cameraTopView = (-22.78, -41.65, 31.53, -51.55, -42.68, -2.96)
          self.timer = None
          self.timerStartTime = None
          return
  
      def getTitle(self):
          return TTLocalizer.RaceGameTitle
  
      def getInstructions(self):
          return TTLocalizer.RaceGameInstructions
  
      def getMaxDuration(self):
          return 60
  
      def load(self):
          self.notify.debug('load')
          DistributedMinigame.load(self)
          self.raceBoard = loader.loadModel('phase_4/models/minigames/race')
          self.raceBoard.setPosHpr(0, 0, 0, 0, 0, 0)
          self.raceBoard.setScale(0.8)
          self.dice = loader.loadModel('phase_4/models/minigames/dice')
          self.dice1 = self.dice.find('**/dice_button1')
          self.dice2 = self.dice.find('**/dice_button2')
          self.dice3 = self.dice.find('**/dice_button3')
          self.dice4 = self.dice.find('**/dice_button4')
          self.diceList = [self.dice1,
           self.dice2,
           self.dice3,
           self.dice4]
          self.music = base.loader.loadMusic('phase_4/audio/bgm/minigame_race.ogg')
          self.posBuzzer = base.loader.loadSfx('phase_4/audio/sfx/MG_pos_buzzer.ogg')
          self.negBuzzer = base.loader.loadSfx('phase_4/audio/sfx/MG_neg_buzzer.ogg')
          self.winSting = base.loader.loadSfx('phase_4/audio/sfx/MG_win.ogg')
          self.loseSting = base.loader.loadSfx('phase_4/audio/sfx/MG_lose.ogg')
          self.diceButtonList = []
          for i in range(1, 5):
              button = self.dice.find('**/dice_button' + str(i))
              button_down = self.dice.find('**/dice_button' + str(i) + '_down')
              button_ro = self.dice.find('**/dice_button' + str(i) + '_ro')
              diceButton = DirectButton(image=(button,
               button_down,
               button_ro,
               None), relief=None, pos=(0.433 + (i - 1) * 0.2, 0.0, 0.15), parent=base.a2dBottomLeft, scale=0.25, command=self.handleInputChoice, extraArgs=[i])
              diceButton.hide()
              self.diceButtonList.append(diceButton)
  
          self.waitingChoicesLabel = DirectLabel(text=TTLocalizer.RaceGameWaitingChoices, text_fg=VBase4(1, 1, 1, 1), relief=None, pos=(-0.6,
                                                                                                                                        0,
                                                                                                                                        -0.75), scale=0.075)
          self.waitingChoicesLabel.hide()
          self.chanceMarker = loader.loadModel('phase_4/models/minigames/question_mark')
          self.chanceCard = loader.loadModel('phase_4/models/minigames/chance_card')
          self.chanceCardText = OnscreenText('', fg=(1.0, 0, 0, 1), scale=0.14, font=ToontownGlobals.getSignFont(), wordwrap=14, pos=(0.0,
                                                                                                                                      0.2), mayChange=1)
          self.chanceCardText.hide()
          self.cardSound = base.loader.loadSfx('phase_3.5/audio/sfx/GUI_stickerbook_turn.ogg')
          self.chanceMarkers = []
          return
  
      def unload(self):
          self.notify.debug('unload')
          DistributedMinigame.unload(self)
          self.raceBoard.removeNode()
          del self.raceBoard
          self.dice.removeNode()
          del self.dice
          self.chanceMarker.removeNode()
          del self.chanceMarker
          self.chanceCardText.removeNode()
          del self.chanceCardText
          self.chanceCard.removeNode()
          del self.chanceCard
          self.waitingChoicesLabel.destroy()
          del self.waitingChoicesLabel
          del self.music
          del self.posBuzzer
          del self.negBuzzer
          del self.winSting
          del self.loseSting
          del self.cardSound
          for button in self.diceButtonList:
              button.destroy()
  
          del self.diceButtonList
          for marker in self.chanceMarkers:
              marker.removeNode()
              del marker
  
          del self.chanceMarkers
          self.removeChildGameFSM(self.gameFSM)
          del self.gameFSM
  
      def onstage(self):
          self.notify.debug('onstage')
          DistributedMinigame.onstage(self)
          base.playMusic(self.music, looping=1, volume=0.8)
          self.raceBoard.reparentTo(render)
          camera.reparentTo(render)
          p = self.cameraTopView
          camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])
          base.transitions.irisIn(0.4)
          base.setBackgroundColor(0.1875, 0.7929, 0)
  
      def offstage(self):
          self.notify.debug('offstage')
          DistributedMinigame.offstage(self)
          self.music.stop()
          base.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
          self.raceBoard.reparentTo(hidden)
          self.chanceCard.reparentTo(hidden)
          self.chanceCardText.hide()
          if hasattr(self, 'chanceMarkers'):
              for marker in self.chanceMarkers:
                  marker.reparentTo(hidden)
  
      def setGameReady(self):
          if not self.hasLocalToon:
              return
          self.notify.debug('setGameReady')
          if DistributedMinigame.setGameReady(self):
              return
          self.resetPositions()
          for i in range(self.numPlayers):
              avId = self.avIdList[i]
              if self.localAvId == avId:
                  self.localAvLane = i
              avatar = self.getAvatar(avId)
              if avatar:
                  avatar.reparentTo(render)
                  avatar.setAnimState('neutral', 1)
                  self.positionInPlace(avatar, i, 0)
  
      def setGameStart(self, timestamp):
          if not self.hasLocalToon:
              return
          self.notify.debug('setGameStart')
          DistributedMinigame.setGameStart(self, timestamp)
          self.gameFSM.request('inputChoice')
  
      def enterOff(self):
          self.notify.debug('enterOff')
  
      def exitOff(self):
          pass
  
      def enterInputChoice(self):
          self.notify.debug('enterInputChoice')
          for button in self.diceButtonList:
              button.show()
  
          self.timer = ToontownTimer.ToontownTimer()
          self.timer.hide()
          if self.timerStartTime != None:
              self.startTimer()
          return
  
      def startTimer(self):
          now = globalClock.getFrameTime()
          elapsed = now - self.timerStartTime
          self.timer.posInTopRightCorner()
          self.timer.setTime(RaceGameGlobals.InputTimeout)
          self.timer.countdown(RaceGameGlobals.InputTimeout - elapsed, self.handleChoiceTimeout)
          self.timer.show()
  
      def setTimerStartTime(self, timestamp):
          if not self.hasLocalToon:
              return
          self.timerStartTime = globalClockDelta.networkToLocalTime(timestamp)
          if self.timer != None:
              self.startTimer()
          return
  
      def exitInputChoice(self):
          for button in self.diceButtonList:
              button.hide()
  
          if self.timer != None:
              self.timer.destroy()
              self.timer = None
          self.timerStartTime = None
          self.ignore('diceButton')
          return
  
      def handleChoiceTimeout(self):
          self.sendUpdate('setAvatarChoice', [0])
          self.gameFSM.request('waitServerChoices')
  
      def handleInputChoice(self, choice):
          self.sendUpdate('setAvatarChoice', [choice])
          self.gameFSM.request('waitServerChoices')
  
      def enterWaitServerChoices(self):
          self.notify.debug('enterWaitServerChoices')
          self.waitingChoicesLabel.show()
  
      def exitWaitServerChoices(self):
          self.waitingChoicesLabel.hide()
  
      def localToonWon(self):
          localToonPosition = self.avatarPositions[self.localAvId]
          if localToonPosition >= RaceGameGlobals.NumberToWin:
              self.notify.debug('localToon won')
              return 1
          return 0
  
      def anyAvatarWon(self):
          for position in self.avatarPositions.values():
              if position >= RaceGameGlobals.NumberToWin:
                  self.notify.debug('anyAvatarWon: Somebody won')
                  return 1
  
          self.notify.debug('anyAvatarWon: Nobody won')
          return 0
  
      def showNumbers(self, task):
          self.notify.debug('showing numbers...')
          self.diceInstanceList = []
          for i in range(len(task.choiceList)):
              avId = self.avIdList[i]
              choice = task.choiceList[i]
              if choice == 0:
                  self.diceInstanceList.append(None)
              else:
                  diceInstance = self.diceList[(choice - 1)].copyTo(self.raceBoard)
                  self.diceInstanceList.append(diceInstance)
                  dicePosition = self.avatarPositions[avId] + 1
                  diceInstance.setScale(4.0)
                  self.positionInPlace(diceInstance, i, dicePosition)
                  diceInstance.setP(-90)
                  diceInstance.setZ(0.05)
  
          return Task.done
  
      def showMatches(self, task):
          self.notify.debug('showing matches...')
          for i in range(len(task.choiceList)):
              avId = self.avIdList[i]
              choice = task.choiceList[i]
              if choice != 0:
                  diceInstance = self.diceInstanceList[i]
                  freq = task.choiceList.count(choice)
                  if freq == 1:
                      diceInstance.setColor(0.2, 1, 0.2, 1)
                      if avId == self.localAvId:
                          base.playSfx(self.posBuzzer)
                  else:
                      diceInstance.setColor(1, 0.2, 0.2, 1)
                      if avId == self.localAvId:
                          base.playSfx(self.negBuzzer)
  
          return Task.done
  
      def hideNumbers(self, task):
          self.notify.debug('hiding numbers...')
          for dice in self.diceInstanceList:
              if dice:
                  dice.removeNode()
  
          self.diceInstanceList = []
          return Task.done
  
      def enterMoveAvatars(self, choiceList, positionList, rewardList):
          self.notify.debug('in enterMoveAvatars:')
          tasks = []
          self.avatarPositionsCopy = self.avatarPositions.copy()
          for i in range(0, len(choiceList) / self.numPlayers):
              startIndex = i * self.numPlayers
              endIndex = startIndex + self.numPlayers
              self.choiceList = choiceList[startIndex:endIndex]
              self.positionList = positionList[startIndex:endIndex]
              self.rewardList = rewardList[startIndex:endIndex]
              self.notify.debug('           turn: ' + str(i + 1))
              self.notify.debug('     choiceList: ' + str(self.choiceList))
              self.notify.debug('   positionList: ' + str(self.positionList))
              self.notify.debug('     rewardList: ' + str(self.rewardList))
              longestLerpTime = self.getLongestLerpTime(i > 0)
              self.notify.debug('longestLerpTime: ' + str(longestLerpTime))
              if i == 0:
                  snt = Task(self.showNumbers)
                  snt.choiceList = self.choiceList
                  smt = Task(self.showMatches)
                  smt.choiceList = self.choiceList
                  tasks += [snt, Task.pause(0.5), smt]
              if longestLerpTime > 0.0:
                  self.notify.debug('someone moved...')
                  mat = Task(self.moveAvatars)
                  mat.choiceList = self.choiceList
                  mat.positionList = self.positionList
                  mat.rewardList = self.rewardList
                  mat.name = 'moveAvatars'
                  if i == 0:
                      tasks += [Task.pause(0.75),
                       mat,
                       Task.pause(0.75),
                       Task(self.hideNumbers),
                       Task.pause(longestLerpTime - 0.5)]
                  else:
                      mat.chance = 1
                      tasks += [mat, Task.pause(longestLerpTime)]
                  tasks += self.showChanceRewards()
              else:
                  self.notify.debug('no one moved...')
                  tasks += [Task.pause(1.0), Task(self.hideNumbers)]
  
          self.notify.debug('task list : ' + str(tasks))
          wdt = Task(self.walkDone)
          wdt.name = 'walk done'
          tasks.append(wdt)
          moveTask = Task.sequence(*tasks)
          taskMgr.add(moveTask, 'moveAvatars')
  
      def walkDone(self, task):
          self.choiceList = []
          self.positionList = []
          if self.anyAvatarWon():
              self.gameFSM.request('winMovie')
          else:
              self.gameFSM.request('inputChoice')
          return Task.done
  
      def getLongestLerpTime(self, afterFirst):
          self.notify.debug('afterFirst: ' + str(afterFirst))
          longestTime = 0.0
          for i in range(len(self.choiceList)):
              freq = self.choiceList.count(self.choiceList[i])
              if afterFirst or freq == 1:
                  oldPosition = self.avatarPositionsCopy[self.avIdList[i]]
                  newPosition = self.positionList[i]
                  self.avatarPositionsCopy[self.avIdList[i]] = newPosition
                  squares_walked = abs(newPosition - oldPosition)
                  longestTime = max(longestTime, self.getWalkDuration(squares_walked))
  
          return longestTime
  
      def showChanceRewards(self):
          tasks = []
          for reward in self.rewardList:
              self.notify.debug('showChanceRewards: reward = ' + str(reward))
              index = self.rewardList.index(reward)
              if reward != -1:
                  self.notify.debug('adding tasks!')
                  hcc = Task(self.hideChanceMarker)
                  hcc.chanceMarkers = self.chanceMarkers
                  hcc.index = index
                  sct = Task(self.showChanceCard)
                  sct.chanceCard = self.chanceCard
                  sct.cardSound = self.cardSound
                  stt = Task(self.showChanceCardText)
                  rewardEntry = RaceGameGlobals.ChanceRewards[reward]
                  stt.rewardIdx = reward
                  if rewardEntry[0][0] < 0 or rewardEntry[0][1] > 0:
                      stt.sound = self.negBuzzer
                  else:
                      stt.sound = self.posBuzzer
                  stt.picker = self.avIdList[index]
                  rct = Task(self.resetChanceCard)
                  task = Task.sequence(hcc, sct, Task.pause(1.0), stt, Task.pause(3.0), rct, Task.pause(0.25))
                  tasks.append(task)
  
          return tasks
  
      def showChanceCard(self, task):
          base.playSfx(task.cardSound)
          self.chanceCard.reparentTo(render)
          quat = Quat()
          quat.setHpr((270, 0, -85.24))
          self.chanceCard.posQuatInterval(1.0, (19.62, 13.41, 13.14), quat, other=camera).start()
          return Task.done
  
      def hideChanceMarker(self, task):
          task.chanceMarkers[task.index].reparentTo(hidden)
          return Task.done
  
      def showChanceCardText(self, task):
          self.notify.debug('showing chance reward: ' + str(task.rewardIdx))
          name = self.getAvatar(task.picker).getName()
          rewardEntry = RaceGameGlobals.ChanceRewards[task.rewardIdx]
          cardText = ''
          if rewardEntry[1] != -1:
              rewardstr_fmt = TTLocalizer.RaceGameCardText
              if rewardEntry[2] > 0:
                  rewardstr_fmt = TTLocalizer.RaceGameCardTextBeans
              cardText = rewardstr_fmt % {'name': name, 'reward': rewardEntry[1]}
          else:
              rewardstr_fmt = TTLocalizer.RaceGameCardTextHi1
              cardText = rewardstr_fmt % {'name': name}
          base.playSfx(task.sound)
          self.chanceCardText.setText(cardText)
          self.chanceCardText.show()
          return Task.done
  
      def resetChanceCard(self, task):
          self.chanceCardText.hide()
          self.chanceCard.reparentTo(hidden)
          self.chanceCard.setPosHpr(0, 0, 0, 0, 0, 0)
          return Task.done
  
      def moveCamera(self):
          bestPosIdx = self.avatarPositions.values()[0]
          best_lane = 0
          cur_lane = 0
          for pos in self.avatarPositions.values():
              if pos > bestPosIdx:
                  bestPosIdx = pos
                  best_lane = cur_lane
              cur_lane = cur_lane + 1
  
          bestPosIdx = min(RaceGameGlobals.NumberToWin, bestPosIdx)
          localToonPosition = self.avatarPositions[self.localAvId]
          savedCamPos = camera.getPos()
          savedCamHpr = camera.getHpr()
          pos1_idx = min(RaceGameGlobals.NumberToWin - 4, localToonPosition)
          pos1 = self.posHprArray[self.localAvLane][pos1_idx]
          bestPosLookAtIdx = bestPosIdx + 1
          localToonLookAtIdx = localToonPosition + 4
          if localToonLookAtIdx >= bestPosLookAtIdx:
              pos2_idx = localToonLookAtIdx
              pos2_idx = min(RaceGameGlobals.NumberToWin, pos2_idx)
              pos2 = self.posHprArray[self.localAvLane][pos2_idx]
          else:
              pos2_idx = bestPosLookAtIdx
              pos2_idx = min(RaceGameGlobals.NumberToWin, pos2_idx)
              pos2 = self.posHprArray[best_lane][pos2_idx]
          posDeltaVecX = pos2[0] - pos1[0]
          posDeltaVecY = pos2[1] - pos1[1]
          DistanceMultiplier = 0.8
          camposX = pos2[0] + DistanceMultiplier * posDeltaVecX
          camposY = pos2[1] + DistanceMultiplier * posDeltaVecY
          race_fraction = bestPosIdx / float(RaceGameGlobals.NumberToWin)
          CamHeight = 10.0 * race_fraction + (1.0 - race_fraction) * 22.0
          CamPos = Vec3(camposX, camposY, pos2[2] + CamHeight)
          camera.setPos(CamPos)
          camera_lookat_idx = min(RaceGameGlobals.NumberToWin - 6, localToonPosition)
          posLookAt = self.posHprArray[self.localAvLane][camera_lookat_idx]
          camera.lookAt(posLookAt[0], posLookAt[1], posLookAt[2])
          CamQuat = Quat()
          CamQuat.setHpr(camera.getHpr())
          camera.setPos(savedCamPos)
          camera.setHpr(savedCamHpr)
          camera.posQuatInterval(0.75, CamPos, CamQuat).start()
  
      def getWalkDuration(self, squares_walked):
          walkDuration = abs(squares_walked / 1.2)
          if squares_walked > 4:
              walkDuration = walkDuration * 0.3
          return walkDuration
  
      def moveAvatars(self, task):
          self.notify.debug('In moveAvatars: ')
          self.notify.debug('    choiceList: ' + str(task.choiceList))
          self.notify.debug('  positionList: ' + str(task.positionList))
          self.notify.debug('  rewardList: ' + str(task.rewardList))
          for i in range(len(self.choiceList)):
              avId = self.avIdList[i]
              choice = task.choiceList[i]
              position = task.positionList[i]
              chance = max(0, hasattr(task, 'chance'))
              if choice != 0:
                  oldPosition = self.avatarPositions[avId]
                  self.avatarPositions[avId] = position
                  self.moveCamera()
                  if not chance and task.choiceList.count(choice) != 1:
                      self.notify.debug('duplicate choice!')
                  else:
                      avatar = self.getAvatar(avId)
                      if avatar:
                          squares_walked = abs(position - oldPosition)
                          if squares_walked > 4:
                              self.notify.debug('running')
                              avatar.setPlayRate(1.0, 'run')
                              self.runInPlace(avatar, i, oldPosition, position, self.getWalkDuration(squares_walked))
                          else:
                              if choice > 0:
                                  self.notify.debug('walking forwards')
                                  avatar.setPlayRate(1.0, 'walk')
                              else:
                                  self.notify.debug('walking backwards')
                                  avatar.setPlayRate(-1.0, 'walk')
                              self.walkInPlace(avatar, i, position, self.getWalkDuration(squares_walked))
  
          return Task.done
  
      def exitMoveAvatars(self):
          self.notify.debug('In exitMoveAvatars: removing hooks')
          taskMgr.remove('moveAvatars')
          return
  
      def gameOverCallback(self, task):
          self.gameOver()
          return Task.done
  
      def enterWinMovie(self):
          self.notify.debug('enterWinMovie')
          if self.localToonWon():
              base.playSfx(self.winSting)
          else:
              base.playSfx(self.loseSting)
          for avId in self.avIdList:
              avPosition = self.avatarPositions[avId]
              if avPosition >= RaceGameGlobals.NumberToWin:
                  avatar = self.getAvatar(avId)
                  if avatar:
                      lane = str(self.avIdList.index(avId))
                      taskMgr.remove('runAvatar-' + lane)
                      taskMgr.remove('walkAvatar-' + lane)
                      avatar.setAnimState('jump', 1.0)
  
          taskMgr.doMethodLater(4.0, self.gameOverCallback, 'playMovie')
  
      def exitWinMovie(self):
          taskMgr.remove('playMovie')
          self.winSting.stop()
          self.loseSting.stop()
  
      def enterCleanup(self):
          self.notify.debug('enterCleanup')
  
      def exitCleanup(self):
          pass
  
      def positionInPlace(self, avatar, lane, place):
          place = min(place, len(self.posHprArray[lane]) - 1)
          posH = self.posHprArray[lane][place]
          avatar.setPosHpr(self.raceBoard, posH[0], posH[1], posH[2], posH[3], 0, 0)
  
      def walkInPlace(self, avatar, lane, place, time):
          place = min(place, len(self.posHprArray[lane]) - 1)
          posH = self.posHprArray[lane][place]
  
          def stopWalk(raceBoard=self.raceBoard, posH=posH):
              avatar.setAnimState('neutral', 1)
              if raceBoard.isEmpty():
                  avatar.setPosHpr(0, 0, 0, 0, 0, 0)
              else:
                  avatar.setPosHpr(raceBoard, posH[0], posH[1], posH[2], posH[3], 0, 0)
  
          posQuat = Quat()
          posQuat.setHpr((posH[3], 0, 0))
          walkSeq = Sequence(Func(avatar.setAnimState, 'walk', 1), avatar.posQuatInterval(time, posH[:3], posQuat, other=self.raceBoard), Func(stopWalk))
          walkSeq.start()
  
      def runInPlace(self, avatar, lane, currentPlace, newPlace, time):
          place = min(newPlace, len(self.posHprArray[lane]) - 1)
          step = (place - currentPlace) / 3
          pos1 = self.posHprArray[lane][(currentPlace + step)]
          pos2 = self.posHprArray[lane][(currentPlace + 2 * step)]
          pos3 = self.posHprArray[lane][place]
  
          def stopRun(raceBoard=self.raceBoard, pos3=pos3):
              avatar.setAnimState('neutral', 1)
              avatar.setPosHpr(raceBoard, pos3[0], pos3[1], pos3[2], pos3[3], 0, 0)
  
          pos1Quat = Quat()
          pos1Quat.setHpr((pos1[3], 0, 0))
          pos2Quat = Quat()
          pos2Quat.setHpr((pos1[3], 0, 0))
          pos3Quat = Quat()
          pos3Quat.setHpr((pos1[3], 0, 0))
          runSeq = Sequence(Func(avatar.setAnimState, 'run', 1), avatar.posQuatInterval(time / 3.0, pos1[:3], pos1Quat, other=self.raceBoard), avatar.posQuatInterval(time / 3.0, pos2[:3], pos2Quat, other=self.raceBoard), avatar.posQuatInterval(time / 3.0, pos3[:3], pos3Quat, other=self.raceBoard), Func(stopRun))
          runSeq.start()
  
      def setAvatarChoice(self, choice):
          self.notify.error('setAvatarChoice should not be called on the client')
  
      def setAvatarChose(self, avId):
          if not self.hasLocalToon:
              return
          self.notify.debug('setAvatarChose: avatar: ' + str(avId) + ' choose a number')
  
      def setChancePositions(self, positions):
          if not self.hasLocalToon:
              return
          row = 0
          for pos in positions:
              marker = self.chanceMarker.copyTo(render)
              posHpr = self.posHprArray[row][pos]
              marker.setPosHpr(self.raceBoard, posHpr[0], posHpr[1], posHpr[2], posHpr[3] + 180, 0, 0.025)
              marker.setScale(0.7)
              self.chanceMarkers.append(marker)
              row += 1
  
      def setServerChoices(self, choices, positions, rewards):
          if not self.hasLocalToon:
              return
          for i in range(len(positions)):
              if positions[i] > RaceGameGlobals.NumberToWin:
                  positions[i] = RaceGameGlobals.NumberToWin
              if positions[i] < 0:
                  positions[i] = 0
  
          self.notify.debug('setServerChoices: %s positions: %s rewards: %s ' % (choices, positions, rewards))
          self.gameFSM.request('moveAvatars', [choices, positions, rewards])
  
      def resetPositions(self):
          for avId in self.avIdList:
              self.avatarPositions[avId] = 0