text: from direct.gui.DirectGui import *
  from direct.directnotify import DirectNotifyGlobal
  from pandac.PandaModules import *
  import NPCToons
  import ToonHead
  import ToonDNA
  from toontown.toonbase import TTLocalizer
  from toontown.toonbase import ToontownGlobals
  from toontown.toonbase import ToontownBattleGlobals
  from toontown.toon import LaughingManGlobals
  
  class NPCFriendPanel(DirectFrame):
      notify = DirectNotifyGlobal.directNotify.newCategory('NPCFriendPanel')
  
      def __init__(self, parent = aspect2d, **kw):
          optiondefs = (('relief', None, None), ('doneEvent', None, None))
          self.defineoptions(kw, optiondefs)
          DirectFrame.__init__(self, parent=parent)
          self.cardList = []
          self.updateLayout()
          self.initialiseoptions(NPCFriendPanel)
          self.accept(localAvatar.uniqueName('maxNPCFriendsChange'), self.updateLayout)
          return None
  
      def update(self, friendDict, fCallable = 0):
          friendList = friendDict.keys()
          for i in xrange(self.maxNPCFriends):
              card = self.cardList[i]
              try:
                  NPCID = friendList[i]
                  count = friendDict[NPCID]
              except IndexError:
                  NPCID = None
                  count = 0
  
              card.update(NPCID, count, fCallable)
  
          return
  
      def updateLayout(self):
          for card in self.cardList:
              card.destroy()
  
          self.cardList = []
          self.maxNPCFriends = localAvatar.getMaxNPCFriends()
          rotateCard = False
          if self.maxNPCFriends == 8:
              rotateCard = True
              xOffset = -5.25
              yOffset = 2.3
              yOffset2 = -4.7
          elif self.maxNPCFriends == 16:
              xOffset = -5.2
              yOffset = 3.5
              yOffset2 = -2.45
          else:
              self.notify.error('got wrong max SOS cards %s' % self.maxNPCFriends)
          count = 0
          for i in xrange(self.maxNPCFriends):
              card = NPCFriendCard(parent=self, rotateCard=rotateCard, doneEvent=self['doneEvent'])
              self.cardList.append(card)
              card.setPos(xOffset, 1, yOffset)
              card.setScale(0.75)
              xOffset += 3.5
              count += 1
              if count % 4 == 0:
                  xOffset = -5.25
                  yOffset += yOffset2
  
  
  class NPCFriendCard(DirectFrame):
      normalTextColor = (0.3, 0.25, 0.2, 1)
      maxRarity = 5
      sosTracks = ToontownBattleGlobals.Tracks + ToontownBattleGlobals.NPCTracks
  
      def __init__(self, parent = aspect2dp, rotateCard = False, **kw):
          optiondefs = (('NPCID', 'Uninitialized', None), ('relief', None, None), ('doneEvent', None, None))
          self.defineoptions(kw, optiondefs)
          DirectFrame.__init__(self, parent=parent)
          self.initialiseoptions(NPCFriendCard)
          cardModel = loader.loadModel('phase_3.5/models/gui/playingCard')
          self.front = DirectFrame(parent=self, relief=None, image=cardModel.find('**/card_front'))
          self.front.hide()
          self.back = DirectFrame(parent=self, relief=None, image=cardModel.find('**/card_back'), geom=cardModel.find('**/logo'))
          callButtonPosZ = -0.9
          textWordWrap = 16.0
          textScale = 0.35
          textPosZ = 1.15
          nameScale = 0.4
          namePosZ = -0.45
          rarityScale = 0.2
          rarityPosZ = -1.2
          self.NPCHeadDim = 1.2
          self.NPCHeadPosZ = 0.45
          self.sosCountInfoPosZ = -0.9
          self.sosCountInfoScale = 0.4
          self.sosCountInfo2PosZ = -0.9
          self.sosCountInfo2Scale = 0.5
          if rotateCard:
              self.front.component('image0').configure(pos=(0, 0, 0.22), hpr=(0, 0, -90), scale=1.35)
              self.back.component('image0').configure(hpr=(0, 0, -90), scale=(-1.35, 1.35, 1.35))
              callButtonPosZ = -2.1
              textWordWrap = 7.0
              textScale = 0.5
              textPosZ = 2.0
              nameScale = 0.5
              namePosZ = -0.89
              rarityScale = 0.25
              rarityPosZ = -2.4
              self.NPCHeadDim = 1.8
              self.NPCHeadPosZ = 0.4
              self.sosCountInfoPosZ = -2.1
              self.sosCountInfoScale = 0.4
              self.sosCountInfo2PosZ = -2.0
              self.sosCountInfo2Scale = 0.55
          self.sosTypeInfo = DirectLabel(parent=self.front, relief=None, text='', text_font=ToontownGlobals.getMinnieFont(), text_fg=self.normalTextColor, text_scale=textScale, text_align=TextNode.ACenter, text_wordwrap=textWordWrap, pos=(0, 0, textPosZ))
          self.NPCHead = None
          self.NPCName = DirectLabel(parent=self.front, relief=None, text='', text_fg=self.normalTextColor, text_scale=nameScale, text_align=TextNode.ACenter, text_wordwrap=8.0, pos=(0, 0, namePosZ))
          buttonModels = loader.loadModel('phase_3.5/models/gui/inventory_gui')
          upButton = buttonModels.find('**/InventoryButtonUp')
          downButton = buttonModels.find('**/InventoryButtonDown')
          rolloverButton = buttonModels.find('**/InventoryButtonRollover')
          self.sosCallButton = DirectButton(parent=self.front, relief=None, text=TTLocalizer.NPCCallButtonLabel, text_fg=self.normalTextColor, text_scale=0.28, text_align=TextNode.ACenter, image=(upButton,
           downButton,
           rolloverButton,
           upButton), image_color=(1.0, 0.2, 0.2, 1), image0_color=Vec4(1.0, 0.4, 0.4, 1), image3_color=Vec4(1.0, 0.4, 0.4, 0.4), image_scale=(4.4, 1, 3.6), image_pos=Vec3(0, 0, 0.08), pos=(-1.15, 0, callButtonPosZ), scale=1.25, command=self.__chooseNPCFriend)
          self.sosCallButton.hide()
          self.sosCountInfo = DirectLabel(parent=self.front, relief=None, text='', text_fg=self.normalTextColor, text_scale=0.75, text_align=TextNode.ALeft, textMayChange=1, pos=(0.0, 0, -1.0))
          star = loader.loadModel('phase_3.5/models/gui/name_star')
          self.rarityStars = []
          for i in xrange(self.maxRarity):
              label = DirectLabel(parent=self.front, relief=None, image=star, image_scale=rarityScale, image_color=Vec4(0.502, 0.251, 0.251, 1.0), pos=(1.1 - i * 0.24, 0, rarityPosZ))
              label.hide()
              self.rarityStars.append(label)
  
          return
  
      def __chooseNPCFriend(self):
          if self['NPCID'] and self['doneEvent']:
              doneStatus = {}
              doneStatus['mode'] = 'NPCFriend'
              doneStatus['friend'] = self['NPCID']
              messenger.send(self['doneEvent'], [doneStatus])
  
      def destroy(self):
          if self.NPCHead:
              self.NPCHead.detachNode()
              self.NPCHead.delete()
          DirectFrame.destroy(self)
  
      def update(self, NPCID, count = 0, fCallable = 0):
          oldNPCID = self['NPCID']
          self['NPCID'] = NPCID
          if NPCID != oldNPCID:
              if self.NPCHead:
                  self.NPCHead.detachNode()
                  self.NPCHead.delete()
              if NPCID is None:
                  self.showBack()
                  return
              self.front.show()
              self.back.hide()
              self.NPCName['text'] = TTLocalizer.NPCToonNames[NPCID]
              self.NPCHead = self.createNPCToonHead(NPCID, dimension=self.NPCHeadDim)
              self.NPCHead.reparentTo(self.front)
              self.NPCHead.setZ(self.NPCHeadPosZ)
              track, level, hp, rarity = NPCToons.getNPCTrackLevelHpRarity(NPCID)
              sosText = self.sosTracks[track]
              if track == ToontownBattleGlobals.NPC_RESTOCK_GAGS:
                  if level == -1:
                      sosText += ' All'
                  else:
                      sosText += ' ' + self.sosTracks[level]
              sosText = TextEncoder.upper(sosText)
              self.sosTypeInfo['text'] = sosText
              for i in xrange(self.maxRarity):
                  if i < rarity:
                      self.rarityStars[i].show()
                  else:
                      self.rarityStars[i].hide()
  
          if fCallable:
              self.sosCallButton.show()
              self.sosCountInfo.setPos(-0.4, 0, self.sosCountInfoPosZ)
              self.sosCountInfo['text_scale'] = self.sosCountInfoScale
              self.sosCountInfo['text_align'] = TextNode.ALeft
          else:
              self.sosCallButton.hide()
              self.sosCountInfo.setPos(0, 0, self.sosCountInfo2PosZ)
              self.sosCountInfo['text_scale'] = self.sosCountInfo2Scale
              self.sosCountInfo['text_align'] = TextNode.ACenter
          if count > 0:
              countText = TTLocalizer.NPCFriendPanelRemaining % count
              self.sosCallButton['state'] = DGG.NORMAL
          else:
              countText = 'Unavailable'
              self.sosCallButton['state'] = DGG.DISABLED
          self.sosCountInfo['text'] = countText
          return
  
      def showFront(self):
          self.front.show()
          self.back.hide()
  
      def showBack(self):
          self.front.hide()
          self.back.show()
  
      def createNPCToonHead(self, NPCID, dimension = 0.5):
          NPCInfo = NPCToons.NPCToonDict[NPCID]
          dnaList = NPCInfo[2]
          gender = NPCInfo[3]
          if dnaList == 'r':
              dnaList = NPCToons.getRandomDNA(NPCID, gender)
          dna = ToonDNA.ToonDNA()
          dna.newToonFromProperties(*dnaList)
          head = ToonHead.ToonHead()
          head.setupHead(dna, forGui=1)
          self.fitGeometry(head, fFlip=1, dimension=dimension)
          if NPCID == 91917:
              LaughingManGlobals.addHeadEffect(head, book=True)
          return head
  
      def fitGeometry(self, geom, fFlip = 0, dimension = 0.5):
          p1 = Point3()
          p2 = Point3()
          geom.calcTightBounds(p1, p2)
          if fFlip:
              t = p1[0]
              p1.setX(-p2[0])
              p2.setX(-t)
          d = p2 - p1
          biggest = max(d[0], d[2])
          s = dimension / biggest
          mid = (p1 + d / 2.0) * s
          geomXform = hidden.attachNewNode('geomXform')
          for child in geom.getChildren():
              child.reparentTo(geomXform)
  
          geomXform.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], 180, 0, 0, s, s, s)
          geomXform.reparentTo(geom)