text: import random
  from pandac.PandaModules import Point3
  from direct.fsm import ClassicFSM
  from direct.fsm import State
  from direct.distributed.ClockDelta import globalClockDelta
  from direct.task import Task
  from toontown.minigame import DistributedMinigameAI
  from toontown.minigame import MinigameGlobals
  from toontown.minigame import CogThiefGameGlobals
  CTGG = CogThiefGameGlobals
  
  class DistributedCogThiefGameAI(DistributedMinigameAI.DistributedMinigameAI):
      notify = directNotify.newCategory('DistributedCogThiefGameAI')
      ExplodeWaitTime = 6.0 + CTGG.LyingDownDuration
  
      def __init__(self, air, minigameId):
          try:
              self.DistributedCogThiefGameAI_initialized
          except:
              self.DistributedCogThiefGameAI_initialized = 1
              DistributedMinigameAI.DistributedMinigameAI.__init__(self, air, minigameId)
              self.gameFSM = ClassicFSM.ClassicFSM('DistributedCogThiefGameAI', [State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive'])], 'inactive', 'inactive')
              self.addChildGameFSM(self.gameFSM)
              self.cogInfo = {}
              self.barrelInfo = {}
              self.initBarrelInfo()
  
      def generate(self):
          self.notify.debug('generate')
          DistributedMinigameAI.DistributedMinigameAI.generate(self)
  
      def delete(self):
          self.notify.debug('delete')
          del self.gameFSM
          self.removeAllTasks()
          DistributedMinigameAI.DistributedMinigameAI.delete(self)
  
      def setGameReady(self):
          self.notify.debug('setGameReady')
          self.initCogInfo()
          DistributedMinigameAI.DistributedMinigameAI.setGameReady(self)
  
      def setGameStart(self, timestamp):
          self.notify.debug('setGameStart')
          DistributedMinigameAI.DistributedMinigameAI.setGameStart(self, timestamp)
          self.gameFSM.request('play')
  
      def setGameAbort(self):
          self.notify.debug('setGameAbort')
          if self.gameFSM.getCurrentState():
              self.gameFSM.request('cleanup')
          DistributedMinigameAI.DistributedMinigameAI.setGameAbort(self)
  
      def gameOver(self):
          self.notify.debug('gameOver')
          score = int(CTGG.calcScore(self.getCurrentGameTime()))
          for avId in self.avIdList:
              self.scoreDict[avId] = score
  
          if self.getNumBarrelsStolen() == 0:
              for avId in self.avIdList:
                  self.scoreDict[avId] += CTGG.PerfectBonus[len(self.avIdList) - 1]
                  self.logPerfectGame(avId)
  
          self.gameFSM.request('cleanup')
          DistributedMinigameAI.DistributedMinigameAI.gameOver(self)
  
      def enterInactive(self):
          self.notify.debug('enterInactive')
  
      def exitInactive(self):
          pass
  
      def enterPlay(self):
          self.notify.debug('enterPlay')
          self.startSuitGoals()
          if not config.GetBool('cog-thief-endless', 0):
              taskMgr.doMethodLater(CTGG.GameTime, self.timerExpired, self.taskName('gameTimer'))
  
      def exitPlay(self):
          pass
  
      def enterCleanup(self):
          self.notify.debug('enterCleanup')
          taskMgr.remove(self.taskName('gameTimer'))
          self.gameFSM.request('inactive')
  
      def exitCleanup(self):
          pass
  
      def initCogInfo(self):
          for cogIndex in xrange(self.getNumCogs()):
              self.cogInfo[cogIndex] = {'pos': Point3(CogThiefGameGlobals.CogStartingPositions[cogIndex]),
               'goal': CTGG.NoGoal,
               'goalId': CTGG.InvalidGoalId,
               'barrel': CTGG.NoBarrelCarried}
  
      def initBarrelInfo(self):
          for barrelIndex in xrange(CogThiefGameGlobals.NumBarrels):
              self.barrelInfo[barrelIndex] = {'pos': Point3(CogThiefGameGlobals.BarrelStartingPositions[barrelIndex]),
               'carriedBy': CTGG.BarrelOnGround,
               'stolen': False}
  
      def makeCogCarryBarrel(self, timestamp, clientStamp, cogIndex, barrelIndex):
          if cogIndex in self.cogInfo and barrelIndex in self.barrelInfo:
              self.barrelInfo[barrelIndex]['carriedBy'] = cogIndex
              self.cogInfo[cogIndex]['barrel'] = barrelIndex
          else:
              self.notify.warning('makeCogCarryBarrel invalid cogIndex=%s barrelIndex=%s' % (cogIndex, barrelIndex))
  
      def b_makeCogCarryBarrel(self, clientStamp, cogIndex, barrelIndex):
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          self.notify.debug('b_makeCogCarryBarrel timeStamp=%s clientStamp=%s cog=%s barrel=%s' % (timestamp,
           clientStamp,
           cogIndex,
           barrelIndex))
          self.makeCogCarryBarrel(timestamp, clientStamp, cogIndex, barrelIndex)
          self.d_makeCogCarryBarrel(timestamp, clientStamp, cogIndex, barrelIndex)
  
      def d_makeCogCarryBarrel(self, timestamp, clientStamp, cogIndex, barrelIndex):
          pos = self.cogInfo[cogIndex]['pos']
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('makeCogCarryBarrel', [timestamp,
           clientStamp,
           cogIndex,
           barrelIndex,
           pos[0],
           pos[1],
           pos[2]])
  
      def makeCogDropBarrel(self, timestamp, clientStamp, cogIndex, barrelIndex, barrelPos):
          if cogIndex in self.cogInfo and barrelIndex in self.barrelInfo:
              self.barrelInfo[barrelIndex]['carriedBy'] = CTGG.BarrelOnGround
              self.cogInfo[cogIndex]['barrel'] = CTGG.NoBarrelCarried
          else:
              self.notify.warning('makeCogDropBarrel invalid cogIndex=%s barrelIndex=%s' % (cogIndex, barrelIndex))
  
      def b_makeCogDropBarrel(self, clientStamp, cogIndex, barrelIndex, barrelPos):
          if self.barrelInfo[barrelIndex]['carriedBy'] != cogIndex:
              self.notify.error("self.barrelInfo[%s]['carriedBy'] != %s" % (barrelIndex, cogIndex))
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          self.makeCogDropBarrel(timestamp, clientStamp, cogIndex, barrelIndex, barrelPos)
          self.d_makeCogDropBarrel(timestamp, clientStamp, cogIndex, barrelIndex, barrelPos)
  
      def d_makeCogDropBarrel(self, timestamp, clientStamp, cogIndex, barrelIndex, barrelPos):
          pos = barrelPos
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('makeCogDropBarrel', [timestamp,
           clientStamp,
           cogIndex,
           barrelIndex,
           pos[0],
           pos[1],
           pos[2]])
  
      def isCogCarryingABarrel(self, cogIndex):
          result = self.cogInfo[cogIndex]['barrel'] > CTGG.NoBarrelCarried
          return result
  
      def isCogCarryingThisBarrel(self, cogIndex, barrelIndex):
          result = self.cogInfo[cogIndex]['barrel'] == barrelIndex
          return result
  
      def startSuitGoals(self):
          delayTimes = []
          for cogIndex in xrange(self.getNumCogs()):
              delayTimes.append(cogIndex * 1.0)
  
          random.shuffle(delayTimes)
          for cogIndex in xrange(self.getNumCogs()):
              self.doMethodLater(delayTimes[cogIndex], self.chooseSuitGoal, self.uniqueName('choseSuitGoal-%d-' % cogIndex), extraArgs=[cogIndex])
  
      def chaseToon(self, suitNum, avId):
          goalType = CTGG.ToonGoal
          goalId = avId
          self.cogInfo[suitNum]['goal'] = goalType
          self.cogInfo[suitNum]['goalId'] = goalId
          pos = self.cogInfo[suitNum]['pos']
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          self.notify.debug('chaseToon time=%s suitNum=%s, avId=%s' % (timestamp, suitNum, avId))
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('updateSuitGoal', [timestamp,
           timestamp,
           suitNum,
           goalType,
           goalId,
           pos[0],
           pos[1],
           pos[2]])
  
      def hitBySuit(self, avId, timestamp, suitNum, x, y, z):
          if suitNum >= self.getNumCogs():
              self.notify.warning('hitBySuit, possible hacker avId=%s' % avId)
              return
          barrelIndex = self.cogInfo[suitNum]['barrel']
          if barrelIndex >= 0:
              barrelPos = Point3(x, y, z)
              self.b_makeCogDropBarrel(timestamp, suitNum, barrelIndex, barrelPos)
          startPos = CTGG.CogStartingPositions[suitNum]
          self.cogInfo[suitNum]['pos'] = startPos
          self.cogInfo[suitNum]['goal'] = CTGG.NoGoal
          self.cogInfo[suitNum]['goalId'] = CTGG.InvalidGoalId
          self.sendSuitSync(timestamp, suitNum)
          self.doMethodLater(self.ExplodeWaitTime, self.chooseSuitGoal, self.uniqueName('choseSuitGoal-%d-' % suitNum), extraArgs=[suitNum])
  
      def sendSuitSync(self, clientstamp, suitNum):
          pos = self.cogInfo[suitNum]['pos']
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          goalType = self.cogInfo[suitNum]['goal']
          goalId = self.cogInfo[suitNum]['goalId']
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('updateSuitGoal', [timestamp,
           clientstamp,
           suitNum,
           goalType,
           goalId,
           pos[0],
           pos[1],
           pos[2]])
  
      def chooseSuitGoal(self, suitNum):
          barrelIndex = self.findClosestUnassignedBarrel(suitNum)
          if barrelIndex >= 0:
              self.chaseBarrel(suitNum, barrelIndex)
          else:
              noOneChasing = self.avIdList[:]
              for key in self.cogInfo:
                  if self.cogInfo[key]['goal'] == CTGG.ToonGoal:
                      toonId = self.cogInfo[key]['goalId']
                      if toonId in noOneChasing:
                          noOneChasing.remove(toonId)
  
              chaseToonId = self.avIdList[0]
              if noOneChasing:
                  chaseToonId = random.choice(noOneChasing)
              else:
                  chaseToonId = random.choice(self.avIdList)
              self.chaseToon(suitNum, chaseToonId)
  
      def chaseBarrel(self, suitNum, barrelIndex):
          goalType = CTGG.BarrelGoal
          goalId = barrelIndex
          self.cogInfo[suitNum]['goal'] = goalType
          self.cogInfo[suitNum]['goalId'] = goalId
          pos = self.cogInfo[suitNum]['pos']
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          self.notify.debug('chaseBarrel time=%s suitNum=%s, barrelIndex=%s' % (timestamp, suitNum, barrelIndex))
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('updateSuitGoal', [timestamp,
           timestamp,
           suitNum,
           goalType,
           goalId,
           pos[0],
           pos[1],
           pos[2]])
  
      def getCogCarryingBarrel(self, barrelIndex):
          return self.barrelInfo[barrelIndex]['carriedBy']
  
      def cogHitBarrel(self, clientStamp, cogIndex, barrelIndex, x, y, z):
          if cogIndex >= self.getNumCogs():
              self.notify.warning('cogHitBarrel, possible hacker cogIndex=%s' % cogIndex)
              return
          if barrelIndex >= CTGG.NumBarrels:
              self.notify.warning('cogHitBarrel, possible hacker barrelIndex=%s' % barrelIndex)
              return
          if self.isCogCarryingABarrel(cogIndex):
              self.notify.debug('cog is already carrying a barrel ignore')
              return
          if self.cogInfo[cogIndex]['goal'] == CTGG.NoGoal:
              self.notify.debug('ignoring barrel hit as cog %d has no goal' % cogIndex)
              return
          if self.getCogCarryingBarrel(barrelIndex) == CTGG.BarrelOnGround:
              pos = Point3(x, y, z)
              returnPosIndex = self.chooseReturnPos(cogIndex, pos)
              self.runAway(clientStamp, cogIndex, pos, barrelIndex, returnPosIndex)
  
      def chooseReturnPos(self, cogIndex, cogPos):
          shortestDistance = 10000
          shortestReturnIndex = -1
          for retIndex in xrange(len(CTGG.CogReturnPositions)):
              retPos = CTGG.CogReturnPositions[retIndex]
              distance = (cogPos - retPos).length()
              if distance < shortestDistance:
                  shortestDistance = distance
                  shortestReturnIndex = retIndex
                  self.notify.debug('shortest distance=%s index=%s' % (shortestDistance, shortestReturnIndex))
  
          self.notify.debug('chooseReturnpos returning %s' % shortestReturnIndex)
          return shortestReturnIndex
  
      def runAway(self, clientStamp, cogIndex, cogPos, barrelIndex, returnPosIndex):
          self.cogInfo[cogIndex]['pos'] = cogPos
          self.b_makeCogCarryBarrel(clientStamp, cogIndex, barrelIndex)
          goalType = CTGG.RunAwayGoal
          goalId = returnPosIndex
          self.cogInfo[cogIndex]['goal'] = goalType
          self.cogInfo[cogIndex]['goalId'] = 0
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          gameTime = self.getCurrentGameTime()
          self.sendUpdate('updateSuitGoal', [timestamp,
           clientStamp,
           cogIndex,
           goalType,
           goalId,
           cogPos[0],
           cogPos[1],
           cogPos[2]])
  
      def pieHitSuit(self, avId, timestamp, suitNum, x, y, z):
          if suitNum >= self.getNumCogs():
              self.notify.warning('hitBySuit, possible hacker avId=%s' % avId)
              return
          barrelIndex = self.cogInfo[suitNum]['barrel']
          if barrelIndex >= 0:
              barrelPos = Point3(x, y, z)
              self.b_makeCogDropBarrel(timestamp, suitNum, barrelIndex, barrelPos)
          startPos = CTGG.CogStartingPositions[suitNum]
          self.cogInfo[suitNum]['pos'] = startPos
          self.cogInfo[suitNum]['goal'] = CTGG.NoGoal
          self.cogInfo[suitNum]['goalId'] = CTGG.InvalidGoalId
          self.sendSuitSync(timestamp, suitNum)
          self.doMethodLater(self.ExplodeWaitTime, self.chooseSuitGoal, self.uniqueName('choseSuitGoal-%d-' % suitNum), extraArgs=[suitNum])
  
      def findClosestUnassignedBarrel(self, suitNum):
          possibleBarrels = []
          for key in self.barrelInfo:
              info = self.barrelInfo[key]
              if info['carriedBy'] == CTGG.BarrelOnGround and not info['stolen']:
                  if not self.isCogGoingForBarrel(key):
                      possibleBarrels.append(key)
  
          shortestDistance = 10000
          shortestBarrelIndex = -1
          cogPos = self.cogInfo[suitNum]['pos']
          for possibleIndex in possibleBarrels:
              barrelPos = self.barrelInfo[possibleIndex]['pos']
              distance = (cogPos - barrelPos).length()
              if distance < shortestDistance:
                  shortestDistance = distance
                  shortestBarrelIndex = possibleIndex
  
          return shortestBarrelIndex
  
      def isCogGoingForBarrel(self, barrelIndex):
          result = False
          for suitNum in self.cogInfo:
              cogInfo = self.cogInfo[suitNum]
              if cogInfo['goal'] == CTGG.BarrelGoal and cogInfo['goalId'] == barrelIndex:
                  result = True
                  break
  
          return result
  
      def markBarrelStolen(self, clientStamp, barrelIndex):
          timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
          self.sendUpdate('markBarrelStolen', [timestamp, clientStamp, barrelIndex])
          self.barrelInfo[barrelIndex]['stolen'] = True
  
      def getNumBarrelsStolen(self):
          numStolen = 0
          for barrel in self.barrelInfo.values():
              if barrel['stolen']:
                  numStolen += 1
  
          return numStolen
  
      def cogAtReturnPos(self, clientstamp, cogIndex, barrelIndex):
          if cogIndex not in self.cogInfo or barrelIndex not in self.barrelInfo:
              avId = self.air.getAvatarIdFromSender()
              self.air.writeServerEvent('suspicious cogAtReturnPos avId=%s, cogIndex=%s, barrelIndex=%s' % (avId, cogIndex, barrelIndex))
              return
          if self.cogInfo[cogIndex]['goal'] == CTGG.RunAwayGoal:
              if self.isCogCarryingThisBarrel(cogIndex, barrelIndex):
                  self.markBarrelStolen(clientstamp, barrelIndex)
                  returnPosIndex = self.cogInfo[cogIndex]['goalId']
                  retPos = CTGG.CogReturnPositions[returnPosIndex]
                  self.b_makeCogDropBarrel(clientstamp, cogIndex, barrelIndex, retPos)
                  startPos = CTGG.CogStartingPositions[cogIndex]
                  self.cogInfo[cogIndex]['pos'] = startPos
                  self.cogInfo[cogIndex]['goal'] = CTGG.NoGoal
                  self.cogInfo[cogIndex]['goalId'] = CTGG.InvalidGoalId
                  self.doMethodLater(0.5, self.chooseSuitGoal, self.uniqueName('choseSuitGoal-%d-' % cogIndex), extraArgs=[cogIndex])
                  self.checkForGameOver()
  
      def checkForGameOver(self):
          numStolen = 0
          for key in self.barrelInfo:
              if self.barrelInfo[key]['stolen']:
                  numStolen += 1
  
          self.notify.debug('numStolen = %s' % numStolen)
          if simbase.config.GetBool('cog-thief-check-barrels', 1):
              if not simbase.config.GetBool('cog-thief-endless', 0):
                  if numStolen == len(self.barrelInfo):
                      self.gameOver()
  
      def timerExpired(self, task):
          self.notify.debug('timer expired')
          self.gameOver()
          return Task.done
  
      def getNumCogs(self):
          result = simbase.config.GetInt('cog-thief-num-cogs', 0)
          if not result:
              safezone = self.getSafezoneId()
              result = CTGG.calculateCogs(self.numPlayers, safezone)
          return result