Example #1
0
class Whisper:
    LENGTH_FACTOR = 0.6

    def createSystemMessage(self, message, important = 1):
        try:
            taskMgr.remove('clearSystemMessage-' + self.taskName)
            self.bubble.remove_node()
            self.bubble = None
        except:
            pass

        self.taskName = str(random.uniform(0, 10101010100L))
        msg_color = (0.8, 0.3, 0.6, 0.6)
        sysmsg_data = [[Point3(0.075, 0, -0.2), base.a2dLeftCenter],
         [Point3(-0.6, 0, -0.7), base.a2dRightCenter],
         [Point3(-0.6, 0, -0.2), base.a2dRightCenter],
         [Point3(0.075, 0, -0.7), base.a2dLeftCenter],
         [Point3(0.35, 0, 0.2), base.a2dBottomCenter],
         [Point3(-0.2, 0, 0.2), base.a2dBottomCenter],
         [Point3(-0.8, 0, 0.2), base.a2dBottomCenter]]
        data = random.choice(sysmsg_data)
        sfx = loader.loadSfx('phase_3.5/audio/sfx/GUI_whisper_3.mp3')
        SoundInterval(sfx).start()
        length = math.sqrt(len(message)) / self.LENGTH_FACTOR
        self.bubble = ChatBalloon(loader.loadModel('phase_3/models/props/chatbox_noarrow.bam')).generate(message, CIGlobals.getToonFont(), balloonColor=msg_color)
        self.bubble.reparent_to(data[1])
        self.bubble.set_pos(data[0])
        self.bubble.set_scale(0.05)
        taskMgr.doMethodLater(length, self.clearSystemMessage, 'clearSystemMessage-' + self.taskName)
        return

    def clearSystemMessage(self, task):
        self.bubble.remove_node()
        self.bubble = None
        return task.done
Example #2
0
 def setChatAbsolute(self, chatString = None):
     if not chatString or chatString.isspace() or len(chatString) == 0:
         return
     self.clearChat()
     self.taskId = random.randint(0, 1000000000000000000000000000000L)
     if self.nameTag:
         self.getNameTag().hide()
     if self.isThought(chatString):
         chatString = self.removeThoughtPrefix(chatString)
         bubble = loader.loadModel(CIGlobals.ThoughtBubble)
     else:
         length = math.sqrt(len(chatString)) / self.LENGTH_FACTOR
         if length < self.MIN_LENGTH:
             length = self.MIN_LENGTH
         if length > self.MAX_LENGTH:
             length = self.MAX_LENGTH
         bubble = loader.loadModel(CIGlobals.ChatBubble)
         if self.autoClearChat:
             taskMgr.doMethodLater(length, self.clearChatTask, 'clearAvatarChat-%s' % str(self.taskId))
     if self.avatarType == CIGlobals.Suit:
         font = CIGlobals.getSuitFont()
     else:
         font = CIGlobals.getToonFont()
     self.chatBubble = ChatBalloon(bubble).generate(chatString, font)
     self.chatBubble.setEffect(BillboardEffect.make(Vec3(0, 0, 1), True, False, 3.0, camera, Point3(0, 0, 0)))
     if self.nameTag:
         self.chatBubble.setZ(self.getNameTag().getZ())
     elif self.avatarType == CIGlobals.Suit:
         self.chatBubble.setZ(CIGlobals.SuitNameTagPos[self.head])
     if hasattr(self.avatar, 'getGhost'):
         if not self.avatar.getGhost() or self.avatar.doId == base.localAvatar.doId:
             self.chatBubble.reparentTo(self)
     else:
         self.chatBubble.reparentTo(self)
     LabelScaler().resize(self.chatBubble)
    def setChatAbsolute(self, chatString=None):
        if not chatString or chatString.isspace() or len(chatString) == 0:
            return

        self.clearChat()
        self.taskId = random.randint(0, 1000000000000000000000000000000)
        if self.nameTag:
            self.getNameTag().hide()

        if self.isThought(chatString):
            chatString = self.removeThoughtPrefix(chatString)
            bubble = loader.loadModel(CIGlobals.ThoughtBubble)
        else:
            length = math.sqrt(len(chatString)) / self.LENGTH_FACTOR
            if length < self.MIN_LENGTH:
                length = self.MIN_LENGTH
            if length > self.MAX_LENGTH:
                length = self.MAX_LENGTH
            bubble = loader.loadModel(CIGlobals.ChatBubble)
            if self.autoClearChat:
                taskMgr.doMethodLater(
                    length, self.clearChatTask,
                    "clearAvatarChat-%s" % (str(self.taskId)))

        if self.avatarType == CIGlobals.Suit:
            font = CIGlobals.getSuitFont()
        else:
            font = CIGlobals.getToonFont()

        self.chatBubble = ChatBalloon(bubble).generate(chatString, font)
        self.chatBubble.setEffect(
            BillboardEffect.make(Vec3(0, 0, 1), True, False, 3.0, camera,
                                 Point3(0, 0, 0)))
        if self.nameTag:
            self.chatBubble.setZ(self.getNameTag().getZ())
        else:
            if self.avatarType == CIGlobals.Suit:
                self.chatBubble.setZ(CIGlobals.SuitNameTagPos[self.head])

        if hasattr(self.avatar, 'getGhost'):
            if not self.avatar.getGhost(
            ) or self.avatar.doId == base.localAvatar.doId:
                self.chatBubble.reparentTo(self)
        else:
            self.chatBubble.reparentTo(self)

        LabelScaler().resize(self.chatBubble)
Example #4
0
 def draw(self):
     if self.isClickable():
         foreground, background = self.whisperColor[self.clickState]
     else:
         foreground, background = self.whisperColor[PGButton.SInactive]
     self.chatBalloon = ChatBalloon(NametagGlobals.chatBalloon2dModel,
                                    NametagGlobals.chatBalloon2dWidth,
                                    NametagGlobals.chatBalloon2dHeight,
                                    self.textNode,
                                    foreground=foreground,
                                    background=background)
     self.chatBalloon.reparentTo(self.contents)
     left, right, bottom, top = self.textNode.getFrameActual()
     center = self.contents.getRelativePoint(self.chatBalloon.textNodePath,
                                             ((left + right) / 2.0, 0,
                                              (bottom + top) / 2.0))
     self.chatBalloon.setPos(self.chatBalloon, -center)
Example #5
0
 def drawChatBalloon(self, model, modelWidth, modelHeight):
     if self.chatFont is None:
         return
     if self.avatar.avatarType == CIGlobals.Suit and len(
             self.getText().split('\n')) == 3:
         name, dept, level = self.getText().split('\n')
     else:
         name = self.getText()
     self.chatTextNode.setText(name + ': ' + self.actualChatText)
     self.setPriority(MarginGlobals.MP_normal)
     if self.textNodePath is not None:
         self.textNodePath.removeNode()
         self.textNodePath = None
     if self.arrow is not None:
         self.arrow.removeNode()
         self.arrow = None
     if self.isClickable():
         foreground, background = self.chatColor[self.clickState]
     else:
         foreground, background = self.chatColor[PGButton.SInactive]
     if self.chatType == NametagGlobals.SPEEDCHAT:
         background = self.speedChatColor
     if background[3] > self.CHAT_BALLOON_ALPHA:
         background = VBase4(background[0], background[1], background[2],
                             self.CHAT_BALLOON_ALPHA)
     self.chatBalloon = ChatBalloon(model,
                                    modelWidth,
                                    modelHeight,
                                    self.chatTextNode,
                                    foreground=foreground,
                                    background=background,
                                    reversed=self.chatReversed,
                                    button=self.chatButton[self.clickState])
     self.chatBalloon.reparentTo(self.contents)
     left, right, bottom, top = self.chatTextNode.getFrameActual()
     center = self.contents.getRelativePoint(self.chatBalloon.textNodePath,
                                             ((left + right) / 2.0, 0,
                                              (bottom + top) / 2.0))
     self.chatBalloon.setPos(self.chatBalloon, -center)
     return
 def drawChatBalloon(self, model, modelWidth, modelHeight):
     if self.chatFont is None:
         return
     if self.isClickable():
         foreground, background = self.chatColor[self.clickState]
     else:
         foreground, background = self.chatColor[PGButton.SInactive]
     if self.chatType == NametagGlobals.SPEEDCHAT:
         background = self.speedChatColor
     if background[3] > self.CHAT_BALLOON_ALPHA:
         background = VBase4(background[0], background[1], background[2],
                             self.CHAT_BALLOON_ALPHA)
     self.chatBalloon = ChatBalloon(model,
                                    modelWidth,
                                    modelHeight,
                                    self.chatTextNode,
                                    foreground=foreground,
                                    background=background,
                                    reversed=self.chatReversed,
                                    button=self.chatButton[self.clickState])
     self.chatBalloon.reparentTo(self.contents)
     return
Example #7
0
    def setChat(self, chat):
        if chat == "":
            return
        self.nameTag.hide()
        try:
            self.bubble.remove()
            taskMgr.remove("RemoveMickeyChat-" + str(self.random_taskid))
        except:
            pass
        self.chat = chat
        self.it = loader.loadFont(CIGlobals.ToonFont,
                                  lineHeight=CIGlobals.ToonFontLineHeight)
        b = loader.loadTexture("phase_3/maps/chatbubble.jpg",
                               "phase_3/maps/chatbubble_a.rgb")

        self.balloon_sfx = loader.loadSfx(
            "phase_3/audio/sfx/GUI_balloon_popup.mp3")
        self.balloon_sfx.play()

        self.dial = loader.loadSfx("phase_3/audio/dial/mickey.wav")

        self.dial.play()

        self.box = loader.loadModel(CIGlobals.ChatBubble)
        self.ChatBalloon = ChatBalloon(self.box)
        LS = LabelScaler()
        self.bubble = self.ChatBalloon.generate(chat, self.it)
        LS.resize(self.bubble)
        self.bubble.reparentTo(self)
        self.bubble.setZ(self.nameTag.getZ() - 0.3)
        self.bubble.setBillboardPointEye()
        self.random_taskid = random.randint(
            0,
            10000000000000000000000000000000000000000000000000000000000000000000000
        )
        taskMgr.doMethodLater(7, self.delChat,
                              "RemoveMickeyChat-" + str(self.random_taskid))
Example #8
0
class ToonTalker:
    THOUGHT_PREFIX = '.'
    LENGTH_FACTOR = 0.6
    MIN_LENGTH = 5
    MAX_LENGTH = 20

    def __init__(self):
        self.avatar = None
        self.nametag = None
        self.autoClearChat = True
        return

    def setAutoClearChat(self, flag):
        self.autoClearChat = flag

    def setAvatar(self, avatar, nametag):
        self.avatar = avatar
        self.nametag = nametag

    def setChatAbsolute(self, chatString=None):
        if not chatString or chatString.isspace() or len(chatString) == 0:
            return
        self.clearChat()
        self.taskId = random.randint(0, 1000000000000000000000000000000L)
        if self.nameTag:
            self.getNameTag().hide()
        if self.isThought(chatString):
            chatString = self.removeThoughtPrefix(chatString)
            bubble = loader.loadModel(CIGlobals.ThoughtBubble)
        else:
            length = math.sqrt(len(chatString)) / self.LENGTH_FACTOR
            if length < self.MIN_LENGTH:
                length = self.MIN_LENGTH
            if length > self.MAX_LENGTH:
                length = self.MAX_LENGTH
            bubble = loader.loadModel(CIGlobals.ChatBubble)
            if self.autoClearChat:
                taskMgr.doMethodLater(length, self.clearChatTask,
                                      'clearAvatarChat-%s' % str(self.taskId))
        if self.avatarType == CIGlobals.Suit:
            font = CIGlobals.getSuitFont()
        else:
            font = CIGlobals.getToonFont()
        self.chatBubble = ChatBalloon(bubble).generate(chatString, font)
        self.chatBubble.setEffect(
            BillboardEffect.make(Vec3(0, 0, 1), True, False, 3.0, camera,
                                 Point3(0, 0, 0)))
        if self.nameTag:
            self.chatBubble.setZ(self.getNameTag().getZ())
        else:
            if self.avatarType == CIGlobals.Suit:
                self.chatBubble.setZ(CIGlobals.SuitNameTagPos[self.head])
        if hasattr(self.avatar, 'getGhost'):
            if not self.avatar.getGhost(
            ) or self.avatar.doId == base.localAvatar.doId:
                self.chatBubble.reparentTo(self)
        else:
            self.chatBubble.reparentTo(self)
        LabelScaler().resize(self.chatBubble)

    def isThought(self, message):
        if message.isspace():
            return False
        if message[0] == self.THOUGHT_PREFIX:
            return True
        return False

    def removeThoughtPrefix(self, message):
        if self.isThought(message):
            return message[len(self.THOUGHT_PREFIX):]
        notify.warning(
            'attempted to remove a thought prefix on a non-thought message')
        return message

    def clearChatTask(self, task):
        self.clearChat()
        return task.done

    def clearChat(self):
        try:
            self.chatBubble.removeNode()
            del self.chatBubble
        except:
            return

        if hasattr(self.avatar, 'getGhost'):
            if self.nameTag and not self.avatar.getGhost(
            ) or self.nameTag and self.avatar.doId == base.localAvatar.doId:
                self.getNameTag().show()
        else:
            if self.nameTag:
                self.getNameTag().show()
        taskMgr.remove('clearAvatarChat-' + str(self.taskId))
class Nametag3d(Nametag, Clickable3d):
    SCALING_MIN_DISTANCE = 1
    SCALING_MAX_DISTANCE = 50
    SCALING_FACTOR = 0.065

    def __init__(self):
        Nametag.__init__(self)
        Clickable3d.__init__(self, 'Nametag3d')
        self.distance = 0
        self.billboardOffset = 3
        self.doBillboardEffect()

    def destroy(self):
        self.ignoreAll()
        Nametag.destroy(self)
        Clickable3d.destroy(self)

    def getUniqueName(self):
        return 'Nametag3d-' + str(id(self))

    def getChatBalloonModel(self):
        return NametagGlobals.chatBalloon3dModel

    def getChatBalloonWidth(self):
        return NametagGlobals.chatBalloon3dWidth

    def getChatBalloonHeight(self):
        return NametagGlobals.chatBalloon3dHeight

    def setBillboardOffset(self, billboardOffset):
        self.billboardOffset = billboardOffset
        self.doBillboardEffect()

    def getBillboardOffset(self):
        return self.billboardOffset

    def doBillboardEffect(self):
        billboardEffect = BillboardEffect.make(Vec3(0, 0, 1), True, False,
                                               self.billboardOffset, base.cam,
                                               Point3(0, 0, 0))
        self.contents.setEffect(billboardEffect)

    def updateClickRegion(self):
        if self.chatBalloon is not None:
            left = self.chatBalloon.center[0] - self.chatBalloon.width / 2
            right = left + self.chatBalloon.width
            bottom = NametagGlobals.chatBalloon3dHeight - 2.4
            top = bottom + self.chatBalloon.height
            self.setClickRegionFrame(left, right, bottom, top)
        else:
            if self.panel is not None:
                centerX = (self.textNode.getLeft() +
                           self.textNode.getRight()) / 2.0
                centerY = (self.textNode.getBottom() +
                           self.textNode.getTop()) / 2.0
                left = centerX - self.panelWidth / 2.0
                right = centerX + self.panelWidth / 2.0
                bottom = centerY - self.panelHeight / 2.0
                top = centerY + self.panelHeight / 2.0
                self.setClickRegionFrame(left, right, bottom, top)
        return

    def isClickable(self):
        if self.getChatText() and self.hasChatButton():
            return True
        return NametagGlobals.wantActiveNametags and Clickable3d.isClickable(
            self)

    def setClickState(self, clickState):
        if self.isClickable():
            self.applyClickState(clickState)
        else:
            self.applyClickState(PGButton.SInactive)
        Clickable3d.setClickState(self, clickState)

    def enterDepressed(self):
        if self.isClickable():
            base.playSfx(NametagGlobals.clickSound)

    def enterRollover(self):
        if self.isClickable() and self.lastClickState != PGButton.SDepressed:
            base.playSfx(NametagGlobals.rolloverSound)

    def update(self):
        self.contents.node().removeAllChildren()
        Nametag.update(self)

    def tick(self, task):
        distance = self.contents.getPos(base.cam).length()
        if distance < self.SCALING_MIN_DISTANCE:
            distance = self.SCALING_MIN_DISTANCE
        else:
            if distance > self.SCALING_MAX_DISTANCE:
                distance = self.SCALING_MAX_DISTANCE
        if distance != self.distance:
            self.contents.setScale(math.sqrt(distance) * self.SCALING_FACTOR)
            self.distance = distance
        self.updateClickRegion()
        return Task.cont

    def drawChatBalloon(self, model, modelWidth, modelHeight):
        if self.chatFont is None:
            return
        if self.isClickable():
            foreground, background = self.chatColor[self.clickState]
        else:
            foreground, background = self.chatColor[PGButton.SInactive]
        if self.chatType == NametagGlobals.SPEEDCHAT:
            background = self.speedChatColor
        if background[3] > self.CHAT_BALLOON_ALPHA:
            background = VBase4(background[0], background[1], background[2],
                                self.CHAT_BALLOON_ALPHA)
        self.chatBalloon = ChatBalloon(model,
                                       modelWidth,
                                       modelHeight,
                                       self.chatTextNode,
                                       foreground=foreground,
                                       background=background,
                                       reversed=self.chatReversed,
                                       button=self.chatButton[self.clickState])
        self.chatBalloon.reparentTo(self.contents)
        return

    def drawNametag(self):
        if self.font is None:
            return
        if self.icon is not None:
            self.contents.attachNewNode(self.icon)
        if self.isClickable():
            foreground, background = self.nametagColor[self.clickState]
        else:
            foreground, background = self.nametagColor[PGButton.SInactive]
        self.textNode.setTextColor(foreground)
        textNodePath = self.contents.attachNewNode(self.textNode, 1)
        textNodePath.setTransparency(foreground[3] < 1)
        textNodePath.setAttrib(DepthWriteAttrib.make(0))
        textNodePath.setY(self.TEXT_Y_OFFSET)
        self.panel = NametagGlobals.cardModel.copyTo(self.contents, 0)
        self.panel.setColor(background)
        self.panel.setTransparency(background[3] < 1)
        x = (self.textNode.getLeft() + self.textNode.getRight()) / 2.0
        z = (self.textNode.getBottom() + self.textNode.getTop()) / 2.0
        self.panel.setPos(x, 0, z)
        self.panelWidth = self.textNode.getWidth() + self.PANEL_X_PADDING
        self.panelHeight = self.textNode.getHeight() + self.PANEL_Z_PADDING
        self.panel.setScale(self.panelWidth, 1, self.panelHeight)
        return
Example #10
0
class DistributedMickey(DistributedSmoothNode):
    def __init__(self, cr):
        self.cr = cr
        DistributedSmoothNode.__init__(self, cr)
        NodePath.__init__(self, 'Mickey')
        self.name = "Mickey"
        self.anim = ""
        self.chat = ""

        self.mickey = Actor(
            "phase_3/models/char/mickey-1200.bam", {
                "neutral": "phase_3/models/char/mickey-wait.bam",
                "walk": "phase_3/models/char/mickey-walk.bam",
                "run": "phase_3/models/char/mickey-run.bam",
                "left-start": "phase_3.5/models/char/mickey-left-start.bam",
                "left": "phase_3.5/models/char/mickey-left.bam",
                "right-start": "phase_3.5/models/char/mickey-right-start.bam",
                "right": "phase_3.5/models/char/mickey-right.bam"
            })
        self.mickeyEye = self.mickey.controlJoint(None, "modelRoot",
                                                  "joint_pupilR")
        self.mickeyEye.setY(0.025)
        self.mickey.reparentTo(self)
        self.mickey.setScale(1.25)

        for bundle in self.mickey.getPartBundleDict().values():
            bundle = bundle['modelRoot'].getBundle()
            earNull = bundle.findChild('sphere3')
            if not earNull:
                earNull = bundle.findChild('*sphere3')
            earNull.clearNetTransforms()

        for bundle in self.mickey.getPartBundleDict().values():
            charNodepath = bundle['modelRoot'].partBundleNP
            bundle = bundle['modelRoot'].getBundle()
            earNull = bundle.findChild('sphere3')
            if not earNull:
                earNull = bundle.findChild('*sphere3')
            ears = charNodepath.find('**/sphere3')
            if ears.isEmpty():
                ears = charNodepath.find('**/*sphere3')
            ears.clearEffect(CharacterJointEffect.getClassType())
            earRoot = charNodepath.attachNewNode('earRoot')
            earPitch = earRoot.attachNewNode('earPitch')
            earPitch.setP(40.0)
            ears.reparentTo(earPitch)
            earNull.addNetTransform(earRoot.node())
            ears.clearMat()
            ears.node().setPreserveTransform(ModelNode.PTNone)
            ears.setP(-40.0)
            ears.flattenMedium()
            ears.setBillboardAxis()

        self.shadow = loader.loadModel("phase_3/models/props/drop_shadow.bam")
        self.shadow.setScale(0.55)
        self.shadow.flattenMedium()
        self.shadow.setBillboardAxis(4)
        try:
            self.shadowPlacer = ShadowPlacer(base.cTrav, self.shadow,
                                             base.wall_mask, base.floor_mask)
            self.shadowPlacer.on()
        except:
            pass
        self.shadow.reparentTo(self)

        cs = CollisionSphere(0, 0, 0, 2)
        cnode = CollisionNode('mickeyCNode')
        cnode.addSolid(cs)
        rs = CollisionRay(0, 0, 2, 0, 0, -1)
        rnode = CollisionNode('mickeyRNode')
        rnode.addSolid(rs)
        self.cnp = self.attachNewNode(cnode)
        self.cnp.setZ(0.75)
        self.rnp = self.attachNewNode(rnode)

    def mickeyCollisions(self):
        self.cnp.setCollideMask(BitMask32(0))
        self.cnp.node().setFromCollideMask(CIGlobals.WallBitmask)
        self.rnp.setCollideMask(BitMask32(0))
        self.rnp.node().setFromCollideMask(CIGlobals.FloorBitmask)

        ss = CollisionSphere(0, 0, 0, 10)
        snode = CollisionNode('mickeySNode')
        snode.addSolid(ss)
        self.snp = self.attachNewNode(snode)
        self.snp.setZ(0.75)
        self.snp.setCollideMask(BitMask32(0))
        self.snp.node().setFromCollideMask(CIGlobals.EventBitmask)

        pusher = CollisionHandlerPusher()
        pusher.setInPattern("%in")
        pusher.addCollider(self.cnp, self)
        floor = CollisionHandlerFloor()
        floor.setInPattern("%in")
        floor.addCollider(self.rnp, self)
        event = CollisionHandlerEvent()
        event.setInPattern("%fn-into")
        event.setOutPattern("%fn-out")
        base.cTrav.addCollider(self.cnp, pusher)
        base.cTrav.addCollider(self.rnp, floor)
        base.cTrav.addCollider(self.snp, event)

    def setName(self, name):
        self.name = name
        if name == "":
            return
        elif self.name == "minnie":
            self.name = "Minnie"
        try:
            self.nameTag.remove()
            del self.nameTag
        except:
            pass
        self.it = loader.loadFont("phase_3/models/fonts/ImpressBT.ttf")
        self.nameTag = DirectLabel(text=self.name,
                                   text_fg=(0.992188, 0.480469, 0.167969, 1.0),
                                   text_bg=(0.75, 0.75, 0.75, 0.5),
                                   text_wordwrap=8,
                                   text_decal=True,
                                   relief=None,
                                   parent=self)
        self.nameTag.setPos(0, 0, 5)
        self.nameTag.setBillboardPointEye()
        LS = LabelScaler()
        LS.resize(self.nameTag)

    def b_setName(self, name):
        self.d_setName(name)
        self.setName(name)

    def d_setName(self, name):
        self.sendUpdate("setName", [name])

    def setChat(self, chat):
        if chat == "":
            return
        self.nameTag.hide()
        try:
            self.bubble.remove()
            taskMgr.remove("RemoveMickeyChat-" + str(self.random_taskid))
        except:
            pass
        self.chat = chat
        self.it = loader.loadFont(CIGlobals.ToonFont,
                                  lineHeight=CIGlobals.ToonFontLineHeight)
        b = loader.loadTexture("phase_3/maps/chatbubble.jpg",
                               "phase_3/maps/chatbubble_a.rgb")

        self.balloon_sfx = loader.loadSfx(
            "phase_3/audio/sfx/GUI_balloon_popup.mp3")
        self.balloon_sfx.play()

        self.dial = loader.loadSfx("phase_3/audio/dial/mickey.wav")

        self.dial.play()

        self.box = loader.loadModel(CIGlobals.ChatBubble)
        self.ChatBalloon = ChatBalloon(self.box)
        LS = LabelScaler()
        self.bubble = self.ChatBalloon.generate(chat, self.it)
        LS.resize(self.bubble)
        self.bubble.reparentTo(self)
        self.bubble.setZ(self.nameTag.getZ() - 0.3)
        self.bubble.setBillboardPointEye()
        self.random_taskid = random.randint(
            0,
            10000000000000000000000000000000000000000000000000000000000000000000000
        )
        taskMgr.doMethodLater(7, self.delChat,
                              "RemoveMickeyChat-" + str(self.random_taskid))

    def delChat(self, task):
        self.chat = ""
        self.nameTag.show()
        self.bubble.remove()
        return task.done

    def b_setChat(self, chat):
        self.d_setChat(chat)
        self.setChat(chat)

    def d_setChat(self, chat):
        self.sendUpdate("setChat", [chat])

    def getChat(self):
        return self.chat

    def setAnimState(self, anim):
        self.anim = anim
        if "start" in anim:
            self.mickey.play(anim)
        self.mickey.loop(anim)

    def b_setAnimState(self, anim):
        self.d_setAnimState(anim)
        self.setAnimState(anim)

    def d_setAnimState(self, anim):
        self.sendUpdate("setAnimState", [anim])

    def getAnimState(self):
        return self.anim

    def announceGenerate(self):
        DistributedSmoothNode.announceGenerate(self)

        self.reparentTo(render)

    def generate(self):
        DistributedSmoothNode.generate(self)

        self.activateSmoothing(True, False)
        self.startSmooth()

    def disable(self):
        self.stopSmooth()
        self.detachNode()
        DistributedSmoothNode.disable(self)

    def delete(self):
        self.mickey = None
        DistributedSmoothNode.delete(self)
Example #11
0
class Nametag2d(Nametag, Clickable2d, MarginVisible):
    CONTENTS_SCALE = 0.25
    CHAT_TEXT_MAX_ROWS = 6
    CHAT_TEXT_WORD_WRAP = 8
    CHAT_BALLOON_ALPHA = 0.5
    ARROW_OFFSET = -1.0
    ARROW_SCALE = 1.5

    def __init__(self):
        Nametag.__init__(self)
        Clickable2d.__init__(self, 'Nametag2d')
        MarginVisible.__init__(self)
        self.actualChatText = ''
        self.arrow = None
        self.textNodePath = None
        self.contents.setScale(self.CONTENTS_SCALE)
        self.hideThought()
        self.accept('MarginVisible-update', self.update)
        return

    def destroy(self):
        self.ignoreAll()
        Nametag.destroy(self)
        if self.textNodePath is not None:
            self.textNodePath.removeNode()
            self.textNodePath = None
        if self.arrow is not None:
            self.arrow.removeNode()
            self.arrow = None
        Clickable2d.destroy(self)
        return

    def getUniqueName(self):
        return 'Nametag2d-' + str(id(self))

    def getChatBalloonModel(self):
        return NametagGlobals.chatBalloon2dModel

    def getChatBalloonWidth(self):
        return NametagGlobals.chatBalloon2dWidth

    def getChatBalloonHeight(self):
        return NametagGlobals.chatBalloon2dHeight

    def setChatText(self, chatText):
        self.actualChatText = chatText
        Nametag.setChatText(self, chatText)

    def updateClickRegion(self):
        if self.chatBalloon is not None:
            right = self.chatBalloon.width / 2.0
            left = -right
            top = self.chatBalloon.height / 2.0
            bottom = -top
            self.setClickRegionFrame(left, right, bottom, top)
            self.region.setActive(True)
        else:
            if self.panel is not None:
                centerX = (self.textNode.getLeft() +
                           self.textNode.getRight()) / 2.0
                centerY = (self.textNode.getBottom() +
                           self.textNode.getTop()) / 2.0
                left = centerX - self.panelWidth / 2.0
                right = centerX + self.panelWidth / 2.0
                bottom = centerY - self.panelHeight / 2.0
                top = centerY + self.panelHeight / 2.0
                self.setClickRegionFrame(left, right, bottom, top)
                self.region.setActive(True)
            else:
                if self.region is not None:
                    self.region.setActive(False)
        return

    def isClickable(self):
        if self.getChatText() and self.hasChatButton():
            return True
        return NametagGlobals.wantActiveNametags and Clickable2d.isClickable(
            self)

    def setClickState(self, clickState):
        if self.isClickable():
            self.applyClickState(clickState)
        else:
            self.applyClickState(PGButton.SInactive)
        Clickable2d.setClickState(self, clickState)

    def enterDepressed(self):
        if self.isClickable():
            base.playSfx(NametagGlobals.clickSound)

    def enterRollover(self):
        if self.isClickable() and self.lastClickState != PGButton.SDepressed:
            base.playSfx(NametagGlobals.rolloverSound)

    def update(self):
        self.contents.node().removeAllChildren()
        Nametag.update(self)
        if self.cell is not None:
            self.reposition()
            self.updateClickRegion()
        else:
            if self.region is not None:
                self.region.setActive(False)
        return

    def tick(self, task):
        if self.avatar is None or self.avatar.isEmpty():
            return Task.cont
        if self.cell is None or self.arrow is None:
            return Task.cont
        location = self.avatar.getPos(NametagGlobals.me)
        rotation = NametagGlobals.me.getQuat(base.cam)
        camSpacePos = rotation.xform(location)
        arrowRadians = math.atan2(camSpacePos[0], camSpacePos[1])
        arrowDegrees = arrowRadians / math.pi * 180
        self.arrow.setR(arrowDegrees - 90)
        return Task.cont

    def drawChatBalloon(self, model, modelWidth, modelHeight):
        if self.chatFont is None:
            return
        if self.avatar.avatarType == CIGlobals.Suit and len(
                self.getText().split('\n')) == 3:
            name, dept, level = self.getText().split('\n')
        else:
            name = self.getText()
        self.chatTextNode.setText(name + ': ' + self.actualChatText)
        self.setPriority(MarginGlobals.MP_normal)
        if self.textNodePath is not None:
            self.textNodePath.removeNode()
            self.textNodePath = None
        if self.arrow is not None:
            self.arrow.removeNode()
            self.arrow = None
        if self.isClickable():
            foreground, background = self.chatColor[self.clickState]
        else:
            foreground, background = self.chatColor[PGButton.SInactive]
        if self.chatType == NametagGlobals.SPEEDCHAT:
            background = self.speedChatColor
        if background[3] > self.CHAT_BALLOON_ALPHA:
            background = VBase4(background[0], background[1], background[2],
                                self.CHAT_BALLOON_ALPHA)
        self.chatBalloon = ChatBalloon(model,
                                       modelWidth,
                                       modelHeight,
                                       self.chatTextNode,
                                       foreground=foreground,
                                       background=background,
                                       reversed=self.chatReversed,
                                       button=self.chatButton[self.clickState])
        self.chatBalloon.reparentTo(self.contents)
        left, right, bottom, top = self.chatTextNode.getFrameActual()
        center = self.contents.getRelativePoint(self.chatBalloon.textNodePath,
                                                ((left + right) / 2.0, 0,
                                                 (bottom + top) / 2.0))
        self.chatBalloon.setPos(self.chatBalloon, -center)
        return

    def drawNametag(self):
        self.setPriority(MarginGlobals.MP_low)
        if self.textNodePath is not None:
            self.textNodePath.removeNode()
            self.textNodePath = None
        if self.arrow is not None:
            self.arrow.removeNode()
            self.arrow = None
        if self.font is None:
            return
        if self.icon is not None:
            self.contents.attachNewNode(self.icon)
        if self.isClickable():
            foreground, background = self.nametagColor[self.clickState]
        else:
            foreground, background = self.nametagColor[PGButton.SInactive]
        self.textNode.setTextColor(foreground)
        self.textNodePath = self.contents.attachNewNode(self.textNode, 1)
        self.textNodePath.setTransparency(foreground[3] < 1)
        self.textNodePath.setAttrib(DepthWriteAttrib.make(0))
        self.textNodePath.setY(self.TEXT_Y_OFFSET)
        self.panel = NametagGlobals.cardModel.copyTo(self.contents, 0)
        self.panel.setColor(background)
        self.panel.setTransparency(background[3] < 1)
        x = (self.textNode.getLeft() + self.textNode.getRight()) / 2.0
        z = (self.textNode.getBottom() + self.textNode.getTop()) / 2.0
        self.panel.setPos(x, 0, z)
        self.panelWidth = self.textNode.getWidth() + self.PANEL_X_PADDING
        self.panelHeight = self.textNode.getHeight() + self.PANEL_Z_PADDING
        self.panel.setScale(self.panelWidth, 1, self.panelHeight)
        self.arrow = NametagGlobals.arrowModel.copyTo(self.contents)
        self.arrow.setZ(self.ARROW_OFFSET + self.textNode.getBottom())
        self.arrow.setScale(self.ARROW_SCALE)
        self.arrow.setColor(
            NametagGlobals.NametagColors[NametagGlobals.CCOtherPlayer][0][0])
        return

    def marginVisibilityChanged(self):
        if self.cell is not None:
            self.reposition()
            self.updateClickRegion()
        else:
            if self.region is not None:
                self.region.setActive(False)
        return

    def reposition(self):
        if self.contents is None:
            return
        origin = Point3()
        self.contents.setPos(origin)
        if self.chatBalloon is not None:
            self.chatBalloon.removeNode()
            self.chatBalloon = None
            self.contents.node().removeAllChildren()
            if self.cell in base.leftCells or self.cell in base.rightCells:
                text = self.getChatText().replace('\x01WLDisplay\x01',
                                                  '').replace('\x02', '')
                textWidth = self.chatTextNode.calcWidth(text)
                if textWidth / self.CHAT_TEXT_WORD_WRAP > self.CHAT_TEXT_MAX_ROWS:
                    self.chatTextNode.setWordwrap(
                        textWidth / (self.CHAT_TEXT_MAX_ROWS - 0.5))
            else:
                self.chatTextNode.setWordwrap(self.CHAT_TEXT_WORD_WRAP)
            model = self.getChatBalloonModel()
            modelWidth = self.getChatBalloonWidth()
            modelHeight = self.getChatBalloonHeight()
            self.drawChatBalloon(model, modelWidth, modelHeight)
            nodePath = self.chatBalloon.textNodePath
            left, right, bottom, top = self.chatTextNode.getFrameActual()
        else:
            if self.panel is not None:
                nodePath = self.textNodePath
                left, right, bottom, top = self.textNode.getFrameActual()
                bottom -= self.ARROW_SCALE
            else:
                return
        if self.cell in base.bottomCells:
            origin = self.contents.getRelativePoint(
                nodePath, ((left + right) / 2.0, 0, bottom))
        else:
            if self.cell in base.leftCells:
                origin = self.contents.getRelativePoint(
                    nodePath, (left, 0, (bottom + top) / 2.0))
            else:
                if self.cell in base.rightCells:
                    origin = self.contents.getRelativePoint(
                        nodePath, (right, 0, (bottom + top) / 2.0))
        self.contents.setPos(self.contents, -origin)
        return
Example #12
0
class WhisperPopup(Clickable2d, MarginVisible):
    CONTENTS_SCALE = 0.25
    TEXT_MAX_ROWS = 6
    TEXT_WORD_WRAP = 8
    QUIT_BUTTON_SHIFT = (0.42, 0, 0.42)
    WHISPER_TIMEOUT_MIN = 10
    WHISPER_TIMEOUT_MAX = 20

    def __init__(self, text, font, whisperType, timeout=None):
        Clickable2d.__init__(self, 'WhisperPopup')
        MarginVisible.__init__(self)
        self.text = text
        self.font = font
        self.whisperType = whisperType
        if timeout is None:
            self.timeout = len(text) * 0.33
            if self.timeout < self.WHISPER_TIMEOUT_MIN:
                self.timeout = self.WHISPER_TIMEOUT_MIN
            elif self.timeout > self.WHISPER_TIMEOUT_MAX:
                self.timeout = self.WHISPER_TIMEOUT_MAX
        else:
            self.timeout = timeout
        self.active = False
        self.senderName = ''
        self.fromId = 0
        self.isPlayer = 0
        self.contents.setScale(self.CONTENTS_SCALE)
        self.whisperColor = ChatGlobals.WhisperColors[self.whisperType]
        self.textNode = TextNode('text')
        self.textNode.setWordwrap(self.TEXT_WORD_WRAP)
        self.textNode.setTextColor(self.whisperColor[PGButton.SInactive][0])
        self.textNode.setFont(self.font)
        self.textNode.setText(self.text)
        self.chatBalloon = None
        self.quitButton = None
        self.timeoutTaskName = self.getUniqueName() + '-timeout'
        self.timeoutTask = None
        self.quitEvent = self.getUniqueName() + '-quit'
        self.accept(self.quitEvent, self.destroy)
        self.setPriority(MarginGlobals.MP_high)
        self.setVisible(True)
        self.update()
        self.accept('MarginVisible-update', self.update)
        return

    def destroy(self):
        self.ignoreAll()
        if self.timeoutTask is not None:
            taskMgr.remove(self.timeoutTask)
            self.timeoutTask = None
        if self.chatBalloon is not None:
            self.chatBalloon.removeNode()
            self.chatBalloon = None
        if self.quitButton is not None:
            self.quitButton.destroy()
            self.quitButton = None
        self.textNode = None
        Clickable2d.destroy(self)
        return

    def getUniqueName(self):
        return 'WhisperPopup-' + str(id(self))

    def update(self):
        if self.chatBalloon is not None:
            self.chatBalloon.removeNode()
            self.chatBalloon = None
        if self.quitButton is not None:
            self.quitButton.destroy()
            self.quitButton = None
        self.contents.node().removeAllChildren()
        self.draw()
        if self.cell is not None:
            self.reposition()
            self.updateClickRegion()
        else:
            if self.region is not None:
                self.region.setActive(False)
        return

    def draw(self):
        if self.isClickable():
            foreground, background = self.whisperColor[self.clickState]
        else:
            foreground, background = self.whisperColor[PGButton.SInactive]
        self.chatBalloon = ChatBalloon(NametagGlobals.chatBalloon2dModel,
                                       NametagGlobals.chatBalloon2dWidth,
                                       NametagGlobals.chatBalloon2dHeight,
                                       self.textNode,
                                       foreground=foreground,
                                       background=background)
        self.chatBalloon.reparentTo(self.contents)
        left, right, bottom, top = self.textNode.getFrameActual()
        center = self.contents.getRelativePoint(self.chatBalloon.textNodePath,
                                                ((left + right) / 2.0, 0,
                                                 (bottom + top) / 2.0))
        self.chatBalloon.setPos(self.chatBalloon, -center)

    def manage(self, marginManager):
        MarginVisible.manage(self, marginManager)
        base.playSfx(base.cr.whisperNoise)
        self.timeoutTask = taskMgr.doMethodLater(self.timeout, self.unmanage,
                                                 self.timeoutTaskName,
                                                 [marginManager])

    def unmanage(self, marginManager):
        MarginVisible.unmanage(self, marginManager)
        self.destroy()

    def setClickable(self, senderName, fromId, isPlayer=0):
        self.senderName = senderName
        self.fromId = fromId
        self.isPlayer = isPlayer
        self.setClickEvent('clickedWhisper',
                           extraArgs=[senderName, fromId, isPlayer])
        self.setActive(True)

    def applyClickState(self, clickState):
        if self.chatBalloon is not None:
            foreground, background = self.whisperColor[clickState]
            self.chatBalloon.setForeground(foreground)
            self.chatBalloon.setBackground(background)
        return

    def setClickState(self, clickState):
        if self.isClickable():
            self.applyClickState(clickState)
        else:
            self.applyClickState(PGButton.SInactive)
        Clickable2d.setClickState(self, clickState)

    def enterDepressed(self):
        if self.isClickable():
            base.playSfx(NametagGlobals.clickSound)

    def enterRollover(self):
        if self.isClickable() and self.lastClickState != PGButton.SDepressed:
            base.playSfx(NametagGlobals.rolloverSound)

    def updateClickRegion(self):
        if self.chatBalloon is not None:
            right = self.chatBalloon.width / 2.0
            left = -right
            top = self.chatBalloon.height / 2.0
            bottom = -top
            self.setClickRegionFrame(left, right, bottom, top)
            self.region.setActive(True)
        else:
            if self.region is not None:
                self.region.setActive(False)
        if self.quitButton is not None:
            self.quitButton.updateClickRegion()
        return

    def marginVisibilityChanged(self):
        if self.cell is not None:
            self.reposition()
            self.updateClickRegion()
        else:
            if self.region is not None:
                self.region.setActive(False)
        return

    def reposition(self):
        if self.contents is None:
            return
        origin = Point3()
        self.contents.setPos(origin)
        if self.chatBalloon is not None:
            self.chatBalloon.removeNode()
            self.chatBalloon = None
        if self.quitButton is not None:
            self.quitButton.destroy()
            self.quitButton = None
        self.contents.node().removeAllChildren()
        if self.cell in base.leftCells or self.cell in base.rightCells:
            text = self.text.replace('\x01WLDisplay\x01',
                                     '').replace('\x02', '')
            textWidth = self.textNode.calcWidth(text)
            if textWidth / self.TEXT_WORD_WRAP > self.TEXT_MAX_ROWS:
                self.textNode.setWordwrap(textWidth /
                                          (self.TEXT_MAX_ROWS - 0.5))
        else:
            self.textNode.setWordwrap(self.TEXT_WORD_WRAP)
        self.draw()
        left, right, bottom, top = self.textNode.getFrameActual()
        if self.cell in base.bottomCells:
            origin = self.contents.getRelativePoint(
                self.chatBalloon.textNodePath,
                ((left + right) / 2.0, 0, bottom))
        else:
            if self.cell in base.leftCells:
                origin = self.contents.getRelativePoint(
                    self.chatBalloon.textNodePath,
                    (left, 0, (bottom + top) / 2.0))
            else:
                if self.cell in base.rightCells:
                    origin = self.contents.getRelativePoint(
                        self.chatBalloon.textNodePath,
                        (right, 0, (bottom + top) / 2.0))
        self.contents.setPos(self.contents, -origin)
        return
Example #13
0
class ToonTalker:
    THOUGHT_PREFIX = '.'
    LENGTH_FACTOR = 0.6
    MIN_LENGTH = 5
    MAX_LENGTH = 20

    def __init__(self):
        self.avatar = None
        self.nametag = None
        self.autoClearChat = True
        return

    def setAvatar(self, avatar, nametag):
        self.avatar = avatar
        self.nametag = nametag

    def setChatAbsolute(self, chatString = None):
        if not chatString or chatString.isspace() or len(chatString) == 0:
            return
        self.clearChat()
        self.taskId = random.randint(0, 1000000000000000000000000000000L)
        if self.nameTag:
            self.getNameTag().hide()
        if self.isThought(chatString):
            chatString = self.removeThoughtPrefix(chatString)
            bubble = loader.loadModel(CIGlobals.ThoughtBubble)
        else:
            length = math.sqrt(len(chatString)) / self.LENGTH_FACTOR
            if length < self.MIN_LENGTH:
                length = self.MIN_LENGTH
            if length > self.MAX_LENGTH:
                length = self.MAX_LENGTH
            bubble = loader.loadModel(CIGlobals.ChatBubble)
            if self.autoClearChat:
                taskMgr.doMethodLater(length, self.clearChatTask, 'clearAvatarChat-%s' % str(self.taskId))
        if self.avatarType == CIGlobals.Suit:
            font = CIGlobals.getSuitFont()
        else:
            font = CIGlobals.getToonFont()
        self.chatBubble = ChatBalloon(bubble).generate(chatString, font)
        self.chatBubble.setEffect(BillboardEffect.make(Vec3(0, 0, 1), True, False, 3.0, camera, Point3(0, 0, 0)))
        if self.nameTag:
            self.chatBubble.setZ(self.getNameTag().getZ())
        elif self.avatarType == CIGlobals.Suit:
            self.chatBubble.setZ(CIGlobals.SuitNameTagPos[self.head])
        if hasattr(self.avatar, 'getGhost'):
            if not self.avatar.getGhost() or self.avatar.doId == base.localAvatar.doId:
                self.chatBubble.reparentTo(self)
        else:
            self.chatBubble.reparentTo(self)
        LabelScaler().resize(self.chatBubble)

    def isThought(self, message):
        if message.isspace():
            return False
        elif message[0] == self.THOUGHT_PREFIX:
            return True
        else:
            return False

    def removeThoughtPrefix(self, message):
        if self.isThought(message):
            return message[len(self.THOUGHT_PREFIX):]
        else:
            notify.warning('attempted to remove a thought prefix on a non-thought message')
            return message

    def clearChatTask(self, task):
        self.clearChat()
        return task.done

    def clearChat(self):
        try:
            self.chatBubble.removeNode()
            del self.chatBubble
        except:
            return

        if hasattr(self.avatar, 'getGhost'):
            if self.nameTag and not self.avatar.getGhost() or self.nameTag and self.avatar.doId == base.localAvatar.doId:
                self.getNameTag().show()
        elif self.nameTag:
            self.getNameTag().show()
        taskMgr.remove('clearAvatarChat-' + str(self.taskId))
Example #14
0
class Nametag3d(Nametag, Clickable3d):
    SCALING_MIN_DISTANCE = 1
    SCALING_MAX_DISTANCE = 100
    SCALING_FACTOR = 0.065

    def __init__(self):
        Nametag.__init__(self)
        Clickable3d.__init__(self, 'Nametag3d')

        self.cTag = CNametag3d()

        self.distance = 0

        self.card = None
        self.cardNP = None

        self.avatarNode = None

        self.billboardOffset = 3
        self.doBillboardEffect()

    def destroy(self):
        self.ignoreAll()

        self.cTag = None

        Nametag.destroy(self)
        Clickable3d.destroy(self)

    def getUniqueName(self):
        return 'Nametag3d-' + str(id(self))

    def getChatBalloonModel(self):
        return NametagGlobals.chatBalloon3dModel

    def getChatBalloonWidth(self):
        return NametagGlobals.chatBalloon3dWidth

    def getChatBalloonHeight(self):
        return NametagGlobals.chatBalloon3dHeight

    def setBillboardOffset(self, billboardOffset):
        self.billboardOffset = billboardOffset
        self.doBillboardEffect()

    def getBillboardOffset(self):
        return self.billboardOffset

    def doBillboardEffect(self):
        billboardEffect = BillboardEffect.make(Vec3(0, 0, 1), True, False,
                                               self.billboardOffset, base.cam,
                                               Point3(0, 0, 0))
        self.contents.setEffect(billboardEffect)

    def updateClickRegion(self):
        if self.chatBalloon is not None:
            reg = []
            self.cTag.get_chatballoon_region(
                self.chatBalloon.center, NametagGlobals.chatBalloon3dHeight,
                reg)

            self.setClickRegionFrame(*reg)
        elif self.panel is not None:
            reg = []
            self.cTag.get_panel_region(self.textNode, reg)

            self.setClickRegionFrame(*reg)

    def isClickable(self):
        if self.getChatText() and self.hasChatButton():
            return True
        return NametagGlobals.wantActiveNametags and Clickable3d.isClickable(
            self)

    def setClickState(self, clickState):
        if self.isClickable():
            self.applyClickState(clickState)
        else:
            self.applyClickState(PGButton.SInactive)

        Clickable3d.setClickState(self, clickState)

    def enterDepressed(self):
        if self.isClickable():
            base.playSfx(NametagGlobals.clickSound)

    def enterRollover(self):
        if self.isClickable() and (self.lastClickState != PGButton.SDepressed):
            base.playSfx(NametagGlobals.rolloverSound)

    def update(self):
        self.contents.node().removeAllChildren()

        Nametag.update(self)

    def tick(self, task):

        distance = self.contents.getPos(base.cam).length()

        if distance < self.SCALING_MIN_DISTANCE:
            distance = self.SCALING_MIN_DISTANCE
        elif distance > self.SCALING_MAX_DISTANCE:
            distance = self.SCALING_MAX_DISTANCE

        if distance != self.distance:
            self.contents.setScale(
                self.cTag.get_scale(distance, self.SCALING_FACTOR))
            self.distance = distance

        if self.isClickable():
            self.updateClickRegion()

        return Task.cont

    def drawChatBalloon(self, model, modelWidth, modelHeight):
        if self.chatFont is None:
            # We can't draw this without a font.
            return

        if self.isClickable():
            foreground, background = self.chatColor[self.clickState]
        else:
            foreground, background = self.chatColor[PGButton.SInactive]
        if self.chatType == NametagGlobals.SPEEDCHAT:
            background = self.speedChatColor
        if background[3] > self.CHAT_BALLOON_ALPHA:
            background = VBase4(background[0], background[1], background[2],
                                self.CHAT_BALLOON_ALPHA)
        self.chatBalloon = ChatBalloon(model,
                                       modelWidth,
                                       modelHeight,
                                       self.chatTextNode,
                                       foreground=foreground,
                                       background=background,
                                       reversed=self.chatReversed,
                                       button=self.chatButton[self.clickState])
        self.chatBalloon.reparentTo(self.contents)

    def drawNametag(self):
        if self.font is None:
            # We can't draw this without a font.
            return

        # Attach the icon:
        if self.icon is not None:
            self.contents.attachNewNode(self.icon)

        if self.isClickable():
            foreground, background = self.nametagColor[self.clickState]
        else:
            foreground, background = self.nametagColor[PGButton.SInactive]

        # Set the color of the TextNode:
        self.textNode.setTextColor(foreground)

        # Attach the TextNode:
        textNodePath = self.contents.attachNewNode(self.textNode, 1)
        textNodePath.setTransparency(foreground[3] < 1)
        textNodePath.setAttrib(DepthWriteAttrib.make(0))
        textNodePath.setY(self.TEXT_Y_OFFSET)

        # Attach a panel behind the TextNode:
        self.panel = NametagGlobals.cardModel.copyTo(self.contents, 0)
        self.panel.setColor(background)
        self.panel.setTransparency(background[3] < 1)

        # Reposition the panel:
        x = (self.textNode.getLeft() + self.textNode.getRight()) / 2.0
        z = (self.textNode.getBottom() + self.textNode.getTop()) / 2.0
        self.panel.setPos(x, 0, z)

        # Resize the panel:
        self.panelWidth = self.textNode.getWidth() + self.PANEL_X_PADDING
        self.panelHeight = self.textNode.getHeight() + self.PANEL_Z_PADDING
        self.panel.setScale(self.panelWidth, 1, self.panelHeight)