Push TTR nametag code

This commit is contained in:
John Cote 2015-06-24 11:28:44 -04:00
parent 5044724410
commit 84e8ad4c0d
17 changed files with 1606 additions and 3 deletions

View file

@ -1,9 +1,53 @@
import string
NORMAL_CHAT = 1
WHISPER_CHAT = 2
GUILD_CHAT = 3
CREW_CHAT = 4
SHIPPVP_CHAT = 5
ERROR_NONE = None
ERROR_NO_OPEN_CHAT = 1
ERROR_NOT_FRIENDS = 2
ERROR_NO_RECEIVER = 3
ERROR_NO_GUILD_CHAT = 4
ERROR_NO_CREW_CHAT = 5
ERROR_NO_SHIPPVP_CHAT = 6
TYPEDCHAT = 0
SPEEDCHAT_NORMAL = 1
SPEEDCHAT_EMOTE = 2
SPEEDCHAT_CUSTOM = 3
SPEEDCHAT_QUEST = 4
SYSTEMCHAT = 4
GAMECHAT = 5
GUILDCHAT = 6
PARTYCHAT = 7
SPEEDCHAT_QUEST = 8
FRIEND_UPDATE = 9
CREW_UPDATE = 10
GUILD_UPDATE = 11
AVATAR_UNAVAILABLE = 12
SHIPPVPCHAT = 13
GMCHAT = 14
ChatEvent = 'ChatEvent'
NormalChatEvent = 'NormalChatEvent'
SCChatEvent = 'SCChatEvent'
SCCustomChatEvent = 'SCCustomChatEvent'
SCEmoteChatEvent = 'SCEmoteChatEvent'
SCQuestEvent = 'SCQuestEvent'
OnScreen = 0
OffScreen = 1
Thought = 2
ThoughtPrefix = '.'
def isThought(message):
if len(message) == 0:
return 0
elif message.find(ThoughtPrefix, 0, len(ThoughtPrefix)) >= 0:
return 1
else:
return 0
def removeThoughtPrefix(message):
if isThought(message):
return message[len(ThoughtPrefix):]
else:
return message

View file

@ -0,0 +1,162 @@
from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject
from otp.nametag import NametagGlobals
class ClickablePopup(PandaNode, DirectObject):
CS_NORMAL = 0
CS_CLICK = 1
CS_HOVER = 2
CS_DISABLED = 3
def __init__(self, cam=None):
PandaNode.__init__(self, 'popup')
DirectObject.__init__(self)
self.__mwn = NametagGlobals.mouseWatcher
self.__name = 'clickregion-%d' % id(self)
self.__cam = cam
self.__region = MouseWatcherRegion(self.__name, 0, 0, 0, 0)
self.__mwn.addRegion(self.__region)
self.__disabled = False
self.__clicked = False
self.__hovered = False
self.__onscreen = False
self.__clickState = 0
self.__clickArgs = []
self.__clickEvent = ''
self.accept(self.__getEvent(self.__mwn.getEnterPattern()), self.__mouseEnter)
self.accept(self.__getEvent(self.__mwn.getLeavePattern()), self.__mouseLeave)
self.accept(self.__getEvent(self.__mwn.getButtonDownPattern()), self.__buttonDown)
self.accept(self.__getEvent(self.__mwn.getButtonUpPattern()), self.__buttonUp)
def destroy(self):
self.__mwn.removeRegion(self.__region)
self.ignoreAll()
def setClickRegionEvent(self, event, clickArgs=[]):
if event is None:
# The caller is disabling us, so instead:
self.__disabled = True
self.__region.setActive(False)
self.__updateClickState()
else:
self.__clickEvent = event
self.__clickArgs = clickArgs
self.__disabled = False
self.__region.setActive(True)
self.__updateClickState()
def getClickState(self):
return self.__clickState
def clickStateChanged(self):
pass # Intended for subclasses.
def __getEvent(self, pattern):
return pattern.replace('%r', self.__name)
def __mouseEnter(self, region, extra):
self.__hovered = True
self.__updateClickState()
def __mouseLeave(self, region, extra):
self.__hovered = False
self.__updateClickState()
def __buttonDown(self, region, button):
if button == 'mouse1':
self.__clicked = True
self.__updateClickState()
def __buttonUp(self, region, button):
if button == 'mouse1':
self.__clicked = False
self.__updateClickState()
def __updateClickState(self):
if self.__disabled:
state = self.CS_DISABLED
elif self.__clicked:
state = self.CS_CLICK
elif self.__hovered:
state = self.CS_HOVER
else:
state = self.CS_NORMAL
if self.__clickState == state: return
oldState = self.__clickState
self.__clickState = state
if oldState == self.CS_NORMAL and state == self.CS_HOVER:
# Play rollover sound:
base.playSfx(NametagGlobals.rolloverSound)
elif state == self.CS_CLICK:
# Play click sound:
base.playSfx(NametagGlobals.clickSound)
elif oldState == self.CS_CLICK and state == self.CS_HOVER:
# Fire click event:
messenger.send(self.__clickEvent, self.__clickArgs)
self.clickStateChanged()
def updateClickRegion(self, left, right, bottom, top, offset=0):
transform = NodePath.anyPath(self).getNetTransform()
if self.__cam:
# We have a camera, so get its transform and move our net transform
# into the coordinate space of the camera:
camTransform = self.__cam.getNetTransform()
transform = camTransform.invertCompose(transform)
# We must discard the rotational component on our transform, thus:
transform = transform.setQuat(Quat())
# Next, we'll transform the frame into camspace:
mat = transform.getMat()
cTopLeft = mat.xformPoint(Point3(left, 0, top))
cBottomRight = mat.xformPoint(Point3(right, 0, bottom))
# Shift along the offset while in camspace, not worldspace.
if offset:
mid = mat.xformPoint(Point3(0,0,0))
length = mid.length()
shift = mid*(length - offset)/length - mid
cTopLeft += shift
cBottomRight += shift
if self.__cam:
# We must go further and project to screenspace:
lens = self.__cam.node().getLens()
sTopLeft = Point2()
sBottomRight = Point2()
if not (lens.project(Point3(cTopLeft), sTopLeft) and
lens.project(Point3(cBottomRight), sBottomRight)):
# Not on-screen! Disable the click region:
self.__region.setActive(False)
self.__onscreen = False
return
else:
# No cam; the "camspace" (actually just net transform) IS the
# screenspace transform.
sTopLeft = Point2(cTopLeft[0], cTopLeft[2])
sBottomRight = Point2(cBottomRight[0], cBottomRight[2])
sLeft, sTop = sTopLeft
sRight, sBottom = sBottomRight
self.__region.setFrame(sLeft, sRight, sBottom, sTop)
self.__region.setActive(not self.__disabled)
self.__onscreen = True
def stashClickRegion(self):
self.__region.setActive(False)
self.__onscreen = False
def isOnScreen(self):
return self.__onscreen

83
otp/margins/MarginCell.py Normal file
View file

@ -0,0 +1,83 @@
from pandac.PandaModules import *
class MarginCell(NodePath):
def __init__(self, manager):
NodePath.__init__(self, 'cell')
self.manager = manager
self.content = None
self.available = False
self.debugSquare = None
self.debugMode = False
self.setDebug(config.GetBool('want-cell-debug', False))
def setAvailable(self, available):
if not available and self.hasContent():
self.setContent(None)
self.available = available
self.updateDebug()
def setContent(self, content):
if self.content:
self.content._assignedCell = None
self.contentNP.removeNode()
self.content.marginVisibilityChanged()
if content:
content._assignedCell = self
content._lastCell = self
self.contentNP = self.attachNewNode(content)
content.marginVisibilityChanged()
self.content = content
self.updateDebug()
def hasContent(self):
return self.content is not None
def getContent(self):
return self.content
def isAvailable(self):
return self.available
def isFree(self):
return self.isAvailable() and not self.hasContent()
def setDebugColor(self, color):
if not self.debugSquare:
cm = CardMaker('debugSquare')
cm.setFrameFullscreenQuad()
self.debugSquare = self.attachNewNode(cm.generate())
self.debugSquare.setTransparency(1)
self.debugSquare.setY(1)
self.debugSquare.setColor(color)
def updateDebug(self):
if not self.debugMode: return
if self.hasContent():
self.setDebugColor(VBase4(0.0, 0.8, 0.0, 0.5))
elif self.isAvailable():
self.setDebugColor(VBase4(0.0, 0.0, 0.8, 0.5))
else:
self.setDebugColor(VBase4(0.8, 0.0, 0.0, 0.5))
def setDebug(self, status):
if bool(status) == self.debugMode:
return
self.debugMode = status
if self.debugMode:
self.updateDebug()
elif self.debugSquare:
self.debugSquare.removeNode()
self.debugSquare = None

View file

@ -0,0 +1,83 @@
from pandac.PandaModules import *
from MarginCell import MarginCell
import random
class MarginManager(PandaNode):
def __init__(self):
PandaNode.__init__(self, 'margins')
self.cells = set()
self.visiblePopups = set()
def addGridCell(self, x, y, a2d):
# Yucky!
nodePath = NodePath.anyPath(self)
a2d.reparentTo(nodePath)
cell = MarginCell(self)
cell.reparentTo(a2d)
cell.setScale(0.2)
cell.setPos(x, 0, y)
cell.setAvailable(True)
cell.setPythonTag('MarginCell', cell)
self.cells.add(cell)
self.reorganize()
return cell
def setCellAvailable(self, cell, available):
cell = cell.getPythonTag('MarginCell')
cell.setAvailable(available)
self.reorganize()
def addVisiblePopup(self, popup):
self.visiblePopups.add(popup)
self.reorganize()
def removeVisiblePopup(self, popup):
if popup not in self.visiblePopups: return
self.visiblePopups.remove(popup)
self.reorganize()
def reorganize(self):
# First, get all active cells:
activeCells = [cell for cell in self.cells if cell.isAvailable()]
# Next, get all visible popups, sorted by priority:
popups = list(self.visiblePopups)
popups.sort(key=lambda x: -x.getPriority())
# We can only display so many popups, so truncate to the number of active
# margin cells:
popups = popups[:len(activeCells)]
# Now, we need to build up a list of free cells:
freeCells = []
for cell in activeCells:
if not cell.hasContent():
freeCells.append(cell)
elif cell.getContent() in popups:
# It's already displaying something we want to show, so we can
# safely ignore this cell/popup pair:
popups.remove(cell.getContent())
else:
# It's not displaying something we want to see, evict the old
# popup:
cell.setContent(None)
freeCells.append(cell)
# At this point, there should be enough cells to show the popups:
assert len(freeCells) >= len(popups)
# Now we assign the popups:
for popup in popups:
if popup._lastCell in freeCells and popup._lastCell.isFree():
# The last cell it had assigned is available, so let's assign it
# again:
popup._lastCell.setContent(popup)
freeCells.remove(popup._lastCell)
else:
# We assign a cell at random.
cell = random.choice(freeCells)
cell.setContent(popup)
freeCells.remove(cell)

View file

@ -0,0 +1,52 @@
from pandac.PandaModules import *
class MarginPopup:
def __init__(self):
self.__manager = None
self.__visible = False
self.__priority = 0
# The margin management system uses these:
self._assignedCell = None
self._lastCell = None
def setVisible(self, visibility):
visibility = bool(visibility)
if self.__visible == visibility: return
self.__visible = visibility
if self.__manager is not None:
if visibility:
self.__manager.addVisiblePopup(self)
else:
self.__manager.removeVisiblePopup(self)
def getPriority(self):
return self.__priority
def setPriority(self, priority):
self.__priority = priority
if self.__manager is not None:
self.__manager.reorganize()
def isDisplayed(self):
return self._assignedCell is not None
def marginVisibilityChanged(self):
pass # Fired externally when the result of isDisplayed changes. For subclasses.
def manage(self, manager):
if self.__manager:
self.unmanage(self.__manager)
self.__manager = manager
if self.__visible:
manager.addVisiblePopup(self)
def unmanage(self, manager):
if self.__manager is not None:
if self.__visible:
self.__manager.removeVisiblePopup(self)
self.__manager = None

View file

@ -0,0 +1,97 @@
from MarginPopup import *
from ClickablePopup import *
from otp.nametag import NametagGlobals
from otp.nametag.NametagConstants import *
class WhisperPopup(MarginPopup, ClickablePopup):
WTNormal = WTNormal
WTQuickTalker = WTQuickTalker
WTSystem = WTSystem
WTBattleSOS = WTBattleSOS
WTEmote = WTEmote
WTToontownBoardingGroup = WTToontownBoardingGroup
WORDWRAP = 7.5
SCALE_2D = 0.25
def __init__(self, text, font, whisperType, timeout=10.0):
ClickablePopup.__init__(self)
MarginPopup.__init__(self)
self.innerNP = NodePath.anyPath(self).attachNewNode('innerNP')
self.innerNP.setScale(self.SCALE_2D)
self.text = text
self.font = font
self.whisperType = whisperType
self.timeout = timeout
self.active = False
self.fromId = 0
self.left = 0.0
self.right = 0.0
self.top = 0.0
self.bottom = 0.0
self.updateContents()
self.setPriority(2)
self.setVisible(True)
def updateContents(self):
if self.whisperType in WHISPER_COLORS:
cc = self.whisperType
else:
cc = WTSystem
fgColor, bgColor = WHISPER_COLORS[cc][self.getClickState()]
self.innerNP.node().removeAllChildren()
balloon, frame = NametagGlobals.speechBalloon2d.generate(
self.text, self.font, textColor=fgColor, balloonColor=bgColor,
wordWrap=self.WORDWRAP)
balloon.reparentTo(self.innerNP)
# Calculate the center of the TextNode.
text = balloon.find('**/+TextNode')
t = text.node()
self.left, self.right, self.bottom, self.top = t.getFrameActual()
center = self.innerNP.getRelativePoint(text, ((self.left + self.right) / 2., 0, (self.bottom + self.top) / 2.))
# Next translate the balloon along the inverse.
balloon.setPos(balloon, -center)
if self.active and self.fromId:
self.setClickRegionEvent('clickedWhisper', clickArgs=[self.fromId])
def setClickable(self, senderName, fromId, todo=0):
self.active = True
self.fromId = fromId
self.updateContents()
self.__updateClickRegion()
def marginVisibilityChanged(self):
self.__updateClickRegion()
def __updateClickRegion(self):
if self.isDisplayed() and self.active:
self.updateClickRegion(-1, 1, self.bottom, self.top)
else:
self.stashClickRegion()
def clickStateChanged(self):
self.updateContents()
def manage(self, manager):
MarginPopup.manage(self, manager)
taskMgr.doMethodLater(self.timeout, self.unmanage, 'whisper-timeout-%d' % id(self), [manager])
# Manually Clean up
def unmanage(self, manager):
MarginPopup.unmanage(self, manager)
ClickablePopup.destroy(self)
self.innerNP.removeNode()

0
otp/margins/__init__.py Normal file
View file

View file

@ -0,0 +1,93 @@
from pandac.PandaModules import *
class ChatBalloon:
TEXT_SHIFT = (0.1, -0.05, 1.1)
TEXT_SHIFT_REVERSED = -0.05
TEXT_SHIFT_PROP = 0.08
NATIVE_WIDTH = 10.0
MIN_WIDTH = 2.5
MIN_HEIGHT = 1
BUBBLE_PADDING = 0.3
BUBBLE_PADDING_PROP = 0.05
BUTTON_SCALE = 6
BUTTON_SHIFT = (-0.2, 0, 0.6)
FRAME_SHIFT = (0.2, 1.4)
def __init__(self, model):
self.model = model
def generate(self, text, font, textColor=(0,0,0,1), balloonColor=(1,1,1,1),
wordWrap = 10.0, button=None, reversed=False):
root = NodePath('balloon')
# Add balloon geometry:
balloon = self.model.copyTo(root)
top = balloon.find('**/top')
middle = balloon.find('**/middle')
bottom = balloon.find('**/bottom')
balloon.setColor(balloonColor)
if balloonColor[3] < 1.0:
balloon.setTransparency(1)
# Render the text into a TextNode, using the font:
t = root.attachNewNode(TextNode('text'))
t.node().setFont(font)
t.node().setWordwrap(wordWrap)
t.node().setText(text)
t.node().setTextColor(textColor)
width, height = t.node().getWidth(), t.node().getHeight()
# Turn off depth write for the text: The place in the depth buffer is
# held by the chat bubble anyway, and the text renders after the bubble
# so there's no risk of the bubble overwriting the text's pixels.
t.setAttrib(DepthWriteAttrib.make(0))
t.setPos(self.TEXT_SHIFT)
t.setX(t, self.TEXT_SHIFT_PROP*width)
t.setZ(t, height)
if reversed:
# The nametag code wants the text on the left side of the axis,
# rather than on the right side. Therefore, we move the text to the
# opposite side:
t.setX(self.TEXT_SHIFT_REVERSED - self.TEXT_SHIFT_PROP*width - width)
# Give the chat bubble a button, if one is requested:
if button:
np = button.copyTo(root)
np.setPos(t, width, 0, -height)
np.setPos(np, self.BUTTON_SHIFT)
np.setScale(self.BUTTON_SCALE)
# Set a minimum width and height for short or empty messages
if width < self.MIN_WIDTH:
width = self.MIN_WIDTH
if reversed:
t.setX(t, -width/2.0)
else:
t.setX(t, width/2.0)
t.node().setAlign(TextNode.ACenter)
if height < self.MIN_HEIGHT:
height = self.MIN_HEIGHT
t.setX(t, height/2.0)
t.node().setAlign(TextNode.ACenter)
# Set the balloon's size:
width *= 1+self.BUBBLE_PADDING_PROP
width += self.BUBBLE_PADDING
balloon.setSx(width/self.NATIVE_WIDTH)
if reversed:
balloon.setSx(-balloon.getSx())
balloon.setTwoSided(True) # Render the backface of the balloon
middle.setSz(height)
top.setZ(top, height-1)
# Calculate the frame occupied by the balloon:
left, bottom = self.FRAME_SHIFT
if reversed:
left = -left - width
frame = (left, left+width, bottom, bottom+height+1)
return root, frame

156
otp/nametag/Nametag.py Normal file
View file

@ -0,0 +1,156 @@
from NametagConstants import *
import NametagGlobals
from otp.margins.ClickablePopup import ClickablePopup
from otp.otpbase import OTPGlobals
from pandac.PandaModules import *
class Nametag(ClickablePopup):
CName = 1
CSpeech = 2
CThought = 4
NAME_PADDING = 0.2
CHAT_ALPHA = 1.0
DEFAULT_CHAT_WORDWRAP = 10.0
IS_3D = False # 3D variants will set this to True.
def __init__(self):
if self.IS_3D:
ClickablePopup.__init__(self, NametagGlobals.camera)
else:
ClickablePopup.__init__(self)
self.contents = 0 # To be set by subclass.
self.innerNP = NodePath.anyPath(self).attachNewNode('nametag_contents')
self.wordWrap = 7.5
self.chatWordWrap = None
self.font = None
self.speechFont = None
self.name = ''
self.displayName = ''
self.qtColor = VBase4(1,1,1,1)
self.colorCode = CCNormal
self.avatar = None
self.icon = NodePath('icon')
self.frame = (0, 0, 0, 0)
self.nameFg = (0,0,0,1)
self.nameBg = (1,1,1,1)
self.chatFg = (0,0,0,1)
self.chatBg = (1,1,1,1)
self.chatString = ''
self.chatFlags = 0
def destroy(self):
ClickablePopup.destroy(self)
def setContents(self, contents):
self.contents = contents
self.update()
def setAvatar(self, avatar):
self.avatar = avatar
def setChatWordwrap(self, chatWordWrap):
self.chatWordWrap = chatWordWrap
def tick(self):
pass # Does nothing by default.
def clickStateChanged(self):
self.update()
def getButton(self):
cs = self.getClickState()
if self.buttons is None:
return None
elif cs in self.buttons:
return self.buttons[cs]
else:
return self.buttons.get(0)
def update(self):
if self.colorCode in NAMETAG_COLORS:
cc = self.colorCode
else:
cc = CCNormal
self.nameFg, self.nameBg, self.chatFg, self.chatBg = NAMETAG_COLORS[cc][self.getClickState()]
self.innerNP.node().removeAllChildren()
if self.contents & self.CThought and self.chatFlags & CFThought:
self.showThought()
elif self.contents & self.CSpeech and self.chatFlags&CFSpeech:
self.showSpeech()
elif self.contents & self.CName and self.displayName:
self.showName()
def showBalloon(self, balloon, text):
if not self.speechFont:
# If no font is set, we can't display anything yet...
return
color = self.qtColor if (self.chatFlags&CFQuicktalker) else self.chatBg
if color[3] > self.CHAT_ALPHA:
color = (color[0], color[1], color[2], self.CHAT_ALPHA)
reversed = (self.IS_3D and (self.chatFlags&CFReversed))
balloon, frame = balloon.generate(text, self.speechFont, textColor=self.chatFg,
balloonColor=color,
wordWrap=self.chatWordWrap or \
self.DEFAULT_CHAT_WORDWRAP,
button=self.getButton(),
reversed=reversed)
balloon.reparentTo(self.innerNP)
self.frame = frame
def showThought(self):
self.showBalloon(self.getThoughtBalloon(), self.chatString)
def showSpeech(self):
self.showBalloon(self.getSpeechBalloon(), self.chatString)
def showName(self):
if not self.font:
# If no font is set, we can't actually display a name yet...
return
# Create text node:
self.innerNP.attachNewNode(self.icon)
t = self.innerNP.attachNewNode(TextNode('name'), 1)
t.node().setFont(self.font)
t.node().setAlign(TextNode.ACenter)
t.node().setWordwrap(self.wordWrap)
t.node().setText(self.displayName)
t.setColor(self.nameFg)
t.setTransparency(self.nameFg[3] < 1.0)
width, height = t.node().getWidth(), t.node().getHeight()
# Put the actual written name a little in front of the nametag and
# disable depth write so the text appears nice and clear, free from
# z-fighting and bizarre artifacts. The text renders *after* the tag
# behind it, due to both being in the transparency bin,
# so there's really no problem with doing this.
t.setY(-0.05)
t.setAttrib(DepthWriteAttrib.make(0))
# Apply panel behind the text:
panel = NametagGlobals.nametagCardModel.copyTo(self.innerNP, 0)
panel.setPos((t.node().getLeft()+t.node().getRight())/2.0, 0,
(t.node().getTop()+t.node().getBottom())/2.0)
panel.setScale(width + self.NAME_PADDING, 1, height + self.NAME_PADDING)
panel.setColor(self.nameBg)
panel.setTransparency(self.nameBg[3] < 1.0)
self.frame = (t.node().getLeft()-self.NAME_PADDING/2.0,
t.node().getRight()+self.NAME_PADDING/2.0,
t.node().getBottom()-self.NAME_PADDING/2.0,
t.node().getTop()+self.NAME_PADDING/2.0)

113
otp/nametag/Nametag2d.py Normal file
View file

@ -0,0 +1,113 @@
from Nametag import *
from otp.margins.MarginPopup import *
from pandac.PandaModules import *
import math
class Nametag2d(Nametag, MarginPopup):
SCALE_2D = 0.25
CHAT_ALPHA = 0.5
ARROW_OFFSET = -1.0
ARROW_SCALE = 1.5
DEFAULT_CHAT_WORDWRAP = 8.0
def __init__(self):
Nametag.__init__(self)
MarginPopup.__init__(self)
self.contents = self.CName|self.CSpeech
self.chatWordWrap = 7.5
self.arrow = None
self.innerNP.setScale(self.SCALE_2D)
def showBalloon(self, balloon, text):
text = '%s: %s' % (self.name, text)
Nametag.showBalloon(self, balloon, text)
# Next, center the balloon in the cell:
balloon = NodePath.anyPath(self).find('*/balloon')
# Calculate the center of the TextNode.
text = balloon.find('**/+TextNode')
t = text.node()
left, right, bottom, top = t.getFrameActual()
center = self.innerNP.getRelativePoint(text,
((left+right)/2., 0, (bottom+top)/2.))
# Next translate the balloon along the inverse.
balloon.setPos(balloon, -center)
# Also translate the frame:
left, right, bottom, top = self.frame
self.frame = (left-center.getX(), right-center.getX(),
bottom-center.getZ(), top-center.getZ())
# When a balloon is active, we need to be somewhat higher-priority in the
# popup system:
self.setPriority(1)
# Remove our pointer arrow:
if self.arrow is not None:
self.arrow.removeNode()
self.arrow = None
def showName(self):
Nametag.showName(self)
# Revert our priority back to basic:
self.setPriority(0)
# Tack on an arrow:
t = self.innerNP.find('**/+TextNode')
arrowZ = self.ARROW_OFFSET + t.node().getBottom()
self.arrow = NametagGlobals.arrowModel.copyTo(self.innerNP)
self.arrow.setZ(arrowZ)
self.arrow.setScale(self.ARROW_SCALE)
self.arrow.setColor(ARROW_COLORS.get(self.colorCode, self.nameFg))
def update(self):
Nametag.update(self)
self.considerUpdateClickRegion()
def marginVisibilityChanged(self):
self.considerUpdateClickRegion()
def considerUpdateClickRegion(self):
# If we are onscreen, we update our click region:
if self.isDisplayed():
left, right, bottom, top = self.frame
self.updateClickRegion(left*self.SCALE_2D, right*self.SCALE_2D,
bottom*self.SCALE_2D, top*self.SCALE_2D)
else:
self.stashClickRegion()
def tick(self):
# Update the arrow's pointing.
if not self.isDisplayed() or self.arrow is None:
return # No arrow or not onscreen.
if self.avatar is None or self.avatar.isEmpty():
return # No avatar, can't be done.
# Get points needed in calculation:
cam = NametagGlobals.camera or base.cam
toon = NametagGlobals.toon or cam
# libotp calculates this using the offset from localToon->avatar, but
# the orientation from cam. Therefore, we duplicate it like so:
location = self.avatar.getPos(toon)
rotation = toon.getQuat(cam)
camSpacePos = rotation.xform(location)
arrowRadians = math.atan2(camSpacePos[0], camSpacePos[1])
arrowDegrees = arrowRadians/math.pi*180
self.arrow.setR(arrowDegrees - 90)
def getSpeechBalloon(self):
return NametagGlobals.speechBalloon2d
def getThoughtBalloon(self):
return NametagGlobals.thoughtBalloon2d

70
otp/nametag/Nametag3d.py Normal file
View file

@ -0,0 +1,70 @@
from Nametag import *
import NametagGlobals
from NametagConstants import *
from pandac.PandaModules import *
import math
class Nametag3d(Nametag):
WANT_DYNAMIC_SCALING = True
SCALING_FACTOR = 0.055
SCALING_MINDIST = 1
SCALING_MAXDIST = 50
BILLBOARD_OFFSET = 3.0
SHOULD_BILLBOARD = True
IS_3D = True
def __init__(self):
Nametag.__init__(self)
self.contents = self.CName|self.CSpeech|self.CThought
self.bbOffset = self.BILLBOARD_OFFSET
self._doBillboard()
def _doBillboard(self):
if self.SHOULD_BILLBOARD:
self.innerNP.setEffect(BillboardEffect.make(
Vec3(0,0,1),
True,
False,
self.bbOffset,
NodePath(), # Empty; look at scene camera
Point3(0,0,0)))
else:
self.bbOffset = 0.0
def setBillboardOffset(self, bbOffset):
self.bbOffset = bbOffset
self._doBillboard()
def tick(self):
if not self.WANT_DYNAMIC_SCALING:
scale = self.SCALING_FACTOR
else:
# Attempt to maintain the same on-screen size.
distance = self.innerNP.getPos(NametagGlobals.camera).length()
distance = max(min(distance, self.SCALING_MAXDIST), self.SCALING_MINDIST)
scale = math.sqrt(distance)*self.SCALING_FACTOR
self.innerNP.setScale(scale)
# As 3D nametags can move around on their own, we need to update the
# click frame constantly:
path = NodePath.anyPath(self)
if path.isHidden() or (path.getTop() != NametagGlobals.camera.getTop() and
path.getTop() != render2d):
self.stashClickRegion()
else:
left, right, bottom, top = self.frame
self.updateClickRegion(left*scale, right*scale,
bottom*scale, top*scale,
self.bbOffset)
def getSpeechBalloon(self):
return NametagGlobals.speechBalloon3d
def getThoughtBalloon(self):
return NametagGlobals.thoughtBalloon3d

View file

@ -0,0 +1,216 @@
CFNoQuitButton=256
CFPageButton=16
CFQuicktalker=4
CFQuitButton=32
CFReversed=64
CFSndOpenchat=128
CFSpeech=1
CFThought=2
CFTimeout=8
CCNormal = 0
CCNoChat = 1
CCNonPlayer = 2
CCSuit = 3
CCToonBuilding = 4
CCSuitBuilding = 5
CCHouseBuilding = 6
CCSpeedChat = 7
CCFreeChat = 8
NAMETAG_COLORS = {
CCNormal: (
# Normal FG BG
((0.3, 0.3, 0.7, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.3, 0.3, 0.7, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.5, 0.5, 1.0, 1.0), (1.0, 1.0, 1.0, 1.0), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.3, 0.3, 0.7, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCNoChat: (
# Normal FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((1.0, 0.5, 0.5, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((1.0, 0.5, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCNonPlayer: (
# Normal FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.8, 0.4, 0.0, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCSuit: (
# Normal FG BG
((0.2, 0.2, 0.2, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.2, 0.2, 0.2, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.4, 0.4, 0.4, 1.0), (1.0, 1.0, 1.0, 0.7), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.2, 0.2, 0.2, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCSuitBuilding: (
# Normal FG BG
((0.5, 0.5, 0.5, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.5, 0.5, 0.5, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.7, 0.7, 0.7, 1.0), (1.0, 1.0, 1.0, 0.7), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.5, 0.5, 0.5, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCToonBuilding: (
# Normal FG BG
((0.2, 0.6, 0.9, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.2, 0.6, 0.9, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.2, 0.6, 0.9, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.2, 0.6, 0.9, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCHouseBuilding: (
# Normal FG BG
((0.2, 0.6, 0.9, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.2, 0.2, 0.5, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.5, 0.5, 1.0, 1.0), (1.0, 1.0, 1.0, 1.0), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.0, 0.6, 0.2, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCSpeedChat: (
# Normal FG BG
((0.0, 0.6, 0.2, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.0, 0.5, 0.0, 1.0), (0.5, 0.5, 0.5, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.0, 0.7, 0.2, 1.0), (1.0, 1.0, 1.0, 0.7), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.0, 0.6, 0.2, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
CCFreeChat: (
# Normal FG BG
((0.3, 0.3, 0.7, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Click FG BG
((0.2, 0.2, 0.5, 1.0), (0.2, 0.2, 0.2, 0.6), # Name
(1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Hover FG BG
((0.5, 0.5, 1.0, 1.0), (1.0, 1.0, 1.0, 1.0), # Name
(0.0, 0.6, 0.6, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
# Disable FG BG
((0.3, 0.3, 0.7, 1.0), (0.8, 0.8, 0.8, 0.5), # Name
(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)), # Chat
),
}
ARROW_COLORS = {
CCSuit: (0.8, 0.4, 0.0, 1.0),
}
DEFAULT_WORDWRAPS = {
CCNormal: 7.5,
CCNoChat: 7.5,
CCNonPlayer: 7.5,
CCSuit: 7.5,
CCToonBuilding: 8.5,
CCSuitBuilding: 8.5,
CCHouseBuilding: 10.0,
CCSpeedChat: 7.5,
CCFreeChat: 7.5
}
WTNormal = 0
WTQuickTalker = 1
WTSystem = 2
WTBattleSOS = 3
WTEmote = 4
WTToontownBoardingGroup = 5
WHISPER_COLORS = {
WTNormal: (
# Normal FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.6, 0.8, 0.6)),
# Click FG BG
((1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 0.8)),
# Hover FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.7, 0.9, 0.6)),
# Disable FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.7, 0.8, 0.6)),
),
WTQuickTalker: (
# Normal FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.6, 0.8, 0.6)),
# Click FG BG
((1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 0.8)),
# Hover FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.7, 0.9, 0.6)),
# Disable FG BG
((0.0, 0.0, 0.0, 1.0), (0.2, 0.7, 0.8, 0.6)),
),
WTSystem: (
# Normal FG BG
((0.0, 0.0, 0.0, 1.0), (0.8, 0.3, 0.6, 0.6)),
# Click FG BG
((1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 0.8)),
# Hover FG BG
((0.0, 0.0, 0.0, 1.0), (0.8, 0.4, 1.0, 0.6)),
# Disable FG BG
((0.0, 0.0, 0.0, 1.0), (0.8, 0.3, 0.6, 0.6)),
),
# TODO: WTBattleSOS
WTEmote: (
# Normal FG BG
((0.0, 0.0, 0.0, 1.0), (0.9, 0.5, 0.1, 0.6)),
# Click FG BG
((1.0, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 0.8)),
# Hover FG BG
((0.0, 0.0, 0.0, 1.0), (0.9, 0.6, 0.2, 0.6)),
# Disable FG BG
((0.0, 0.0, 0.0, 1.0), (0.9, 0.6, 0.1, 0.6)),
),
# TODO: WTToontownBoardingGroup
}

View file

@ -0,0 +1,7 @@
from Nametag3d import *
class NametagFloat2d(Nametag3d):
WANT_DYNAMIC_SCALING = False
SCALING_FACTOR = 1.0
SHOULD_BILLBOARD = False
IS_3D = False

View file

@ -0,0 +1,7 @@
from Nametag3d import *
class NametagFloat3d(Nametag3d):
WANT_DYNAMIC_SCALING = False
SCALING_FACTOR = 1.0
SHOULD_BILLBOARD = True
IS_3D = False

View file

@ -0,0 +1,100 @@
camera = None
def setCamera(cam):
global camera
camera = cam
arrowModel = None
def setArrowModel(am):
global arrowModel
arrowModel = am
nametagCardModel = None
nametagCardDimensions = None
def setNametagCard(model, dimensions):
global nametagCardModel, nametagCardDimensions
nametagCardModel = model
nametagCardDimensions = dimensions
mouseWatcher = None
def setMouseWatcher(mw):
global mouseWatcher
mouseWatcher = mw
speechBalloon3d = None
def setSpeechBalloon3d(sb3d):
global speechBalloon3d
speechBalloon3d = sb3d
thoughtBalloon3d = None
def setThoughtBalloon3d(tb3d):
global thoughtBalloon3d
thoughtBalloon3d = tb3d
speechBalloon2d = None
def setSpeechBalloon2d(sb2d):
global speechBalloon2d
speechBalloon2d = sb2d
thoughtBalloon2d = None
def setThoughtBalloon2d(tb2d):
global thoughtBalloon2d
thoughtBalloon2d = tb2d
pageButtons = {}
def setPageButton(state, model):
pageButtons[state] = model
quitButtons = {}
def setQuitButton(state, model):
quitButtons[state] = model
rolloverSound = None
def setRolloverSound(ros):
global rolloverSound
rolloverSound = ros
clickSound = None
def setClickSound(cs):
global clickSound
clickSound = cs
toon = None
def setToon(t):
global toon
toon = t
masterArrowsOn = 0
def setMasterArrowsOn(mao):
global masterArrowsOn
masterArrowsOn = mao
masterNametagsActive = 0
def setMasterNametagsActive(mna):
global masterNametagsActive
masterNametagsActive = mna
min2dAlpha = 0.0
def setMin2dAlpha(m2a):
global min2dAlpha
min2dAlpha = m2a
def getMin2dAlpha():
global min2dAlpha
return min2dAlpha
max2dAlpha = 0.0
def setMax2dAlpha(m2a):
global max2dAlpha
max2dAlpha = m2a
def getMax2dAlpha():
global max2dAlpha
return max2dAlpha
onscreenChatForced = 0
def setOnscreenChatForced(ocf):
global onscreenChatForced
onscreenChatForced = ocf
def setGlobalNametagScale(s):
pass

320
otp/nametag/NametagGroup.py Normal file
View file

@ -0,0 +1,320 @@
from pandac.PandaModules import *
from NametagConstants import *
from Nametag3d import *
from Nametag2d import *
class NametagGroup:
CCNormal = CCNormal
CCNoChat = CCNoChat
CCNonPlayer = CCNonPlayer
CCSuit = CCSuit
CCToonBuilding = CCToonBuilding
CCSuitBuilding = CCSuitBuilding
CCHouseBuilding = CCHouseBuilding
CCSpeedChat = CCSpeedChat
CCFreeChat = CCFreeChat
CHAT_TIMEOUT_MAX = 12.0
CHAT_TIMEOUT_MIN = 4.0
CHAT_TIMEOUT_PROP = 0.5
def __init__(self):
self.nametag2d = Nametag2d()
self.nametag3d = Nametag3d()
self.icon = PandaNode('icon')
self.chatTimeoutTask = None
self.font = None
self.speechFont = None
self.name = ''
self.displayName = ''
self.wordWrap = None
self.qtColor = VBase4(1,1,1,1)
self.colorCode = CCNormal
self.avatar = None
self.active = True
self.chatPages = []
self.chatPage = 0
self.chatFlags = 0
self.objectCode = None
self.manager = None
self.nametags = []
self.addNametag(self.nametag2d)
self.addNametag(self.nametag3d)
self.visible3d = True # Is a 3D nametag visible, or do we need a 2D popup?
self.tickTask = taskMgr.add(self.__tickTask, self.getUniqueId(), sort=45)
self.stompTask = None
self.stompText = None
self.stompFlags = 0
def destroy(self):
taskMgr.remove(self.tickTask)
if self.manager is not None:
self.unmanage(self.manager)
for nametag in list(self.nametags):
self.removeNametag(nametag)
if self.stompTask:
self.stompTask.remove()
def getNametag2d(self):
return self.nametag2d
def getNametag3d(self):
return self.nametag3d
def getNameIcon(self):
return self.icon
def getNumChatPages(self):
if not self.chatFlags & (CFSpeech|CFThought):
return 0
return len(self.chatPages)
def setPageNumber(self, page):
self.chatPage = page
self.updateTags()
def getChatStomp(self):
return bool(self.stompTask)
def getChat(self):
if self.chatPage >= len(self.chatPages):
return ''
else:
return self.chatPages[self.chatPage]
def getStompText(self):
return self.stompText
def getStompDelay(self):
return 0.2
def getUniqueId(self):
return 'Nametag-%d' % id(self)
def hasButton(self):
return bool(self.getButtons())
def getButtons(self):
if self.getNumChatPages() < 2:
# Either only one page or no pages displayed. This means no button,
# unless the game code specifically requests one.
if self.chatFlags & CFQuitButton:
return NametagGlobals.quitButtons
elif self.chatFlags & CFPageButton:
return NametagGlobals.pageButtons
else:
return None
elif self.chatPage == self.getNumChatPages()-1:
# Last page of a multiple-page chat. This calls for a quit button,
# unless the game says otherwise.
if not self.chatFlags & CFNoQuitButton:
return NametagGlobals.quitButtons
else:
return None
else:
# Non-last page of a multiple-page chat. This calls for a page
# button, but only if the game requests it:
if self.chatFlags & CFPageButton:
return NametagGlobals.pageButtons
else:
return None
def setActive(self, active):
self.active = active
def isActive(self):
return self.active
def setAvatar(self, avatar):
self.avatar = avatar
def setFont(self, font):
self.font = font
self.updateTags()
def setSpeechFont(self, font):
self.speechFont = font
self.updateTags()
def setWordwrap(self, wrap):
self.wordWrap = wrap
self.updateTags()
def setColorCode(self, cc):
self.colorCode = cc
self.updateTags()
def setName(self, name):
self.name = name
self.updateTags()
def setDisplayName(self, name):
self.displayName = name
self.updateTags()
def setQtColor(self, color):
self.qtColor = color
self.updateTags()
def setChat(self, chatString, chatFlags):
if not self.chatFlags&CFSpeech:
# We aren't already displaying some chat. Therefore, we don't have
# to stomp.
self._setChat(chatString, chatFlags)
else:
# Stomp!
self.clearChat()
self.stompText = chatString
self.stompFlags = chatFlags
self.stompTask = taskMgr.doMethodLater(self.getStompDelay(), self.__updateStomp,
'ChatStomp-' + self.getUniqueId())
def _setChat(self, chatString, chatFlags):
if chatString:
self.chatPages = chatString.split('\x07')
self.chatFlags = chatFlags
else:
self.chatPages = []
self.chatFlags = 0
self.setPageNumber(0) # Calls updateTags() for us.
self._stopChatTimeout()
if chatFlags&CFTimeout:
self._startChatTimeout()
def __updateStomp(self, task):
self._setChat(self.stompText, self.stompFlags)
self.stompTask = None
def setContents(self, contents):
# This function is a little unique, it's meant to override contents on
# EXISTING nametags only:
for tag in self.nametags:
tag.setContents(contents)
def setObjectCode(self, objectCode):
self.objectCode = objectCode
def getObjectCode(self):
return self.objectCode
def _startChatTimeout(self):
length = len(self.getChat())
timeout = min(max(length*self.CHAT_TIMEOUT_PROP, self.CHAT_TIMEOUT_MIN), self.CHAT_TIMEOUT_MAX)
self.chatTimeoutTask = taskMgr.doMethodLater(timeout, self.__doChatTimeout,
'ChatTimeout-' + self.getUniqueId())
def __doChatTimeout(self, task):
self._setChat('', 0)
return task.done
def _stopChatTimeout(self):
if self.chatTimeoutTask:
taskMgr.remove(self.chatTimeoutTask)
def clearShadow(self):
pass
def clearChat(self):
self._setChat('', 0)
if self.stompTask:
self.stompTask.remove()
def updateNametag(self, tag):
tag.font = self.font
tag.speechFont = self.speechFont
tag.name = self.name
tag.wordWrap = self.wordWrap or DEFAULT_WORDWRAPS[self.colorCode]
tag.displayName = self.displayName or self.name
tag.qtColor = self.qtColor
tag.colorCode = self.colorCode
tag.chatString = self.getChat()
tag.buttons = self.getButtons()
tag.chatFlags = self.chatFlags
tag.avatar = self.avatar
tag.icon = self.icon
tag.update()
def __testVisible3D(self):
# We must determine if a 3D nametag is visible or not, since this
# affects the visibility state of 2D nametags.
# Next, we iterate over all of our nametags until we find a visible
# one:
for nametag in self.nametags:
if not isinstance(nametag, Nametag3d):
continue # It's not in the 3D system, disqualified.
if nametag.isOnScreen():
return True
# If we got here, none of the tags were a match...
return False
def __tickTask(self, task):
for nametag in self.nametags:
nametag.tick()
if (NametagGlobals.masterNametagsActive and self.active) or self.hasButton():
nametag.setClickRegionEvent(self.getUniqueId())
else:
nametag.setClickRegionEvent(None)
if NametagGlobals.onscreenChatForced and self.chatFlags & CFSpeech:
# Because we're *forcing* chat onscreen, we skip the visible3d test
# and go ahead and display it anyway.
visible3d = False
elif not NametagGlobals.masterArrowsOn and not self.chatFlags:
# We're forcing margins offscreen; therefore, we should pretend
# that the 3D nametag is always visible.
visible3d = True
else:
visible3d = self.__testVisible3D()
if visible3d ^ self.visible3d:
self.visible3d = visible3d
for nametag in self.nametags:
if isinstance(nametag, MarginPopup):
nametag.setVisible(not visible3d)
return task.cont
def updateTags(self):
for nametag in self.nametags:
self.updateNametag(nametag)
def addNametag(self, nametag):
self.nametags.append(nametag)
self.updateNametag(nametag)
if self.manager is not None and isinstance(nametag, MarginPopup):
nametag.manage(manager)
def removeNametag(self, nametag):
self.nametags.remove(nametag)
if self.manager is not None and isinstance(nametag, MarginPopup):
nametag.unmanage(manager)
nametag.destroy()
def manage(self, manager):
self.manager = manager
for tag in self.nametags:
if isinstance(tag, MarginPopup):
tag.manage(manager)
def unmanage(self, manager):
self.manager = None
for tag in self.nametags:
if isinstance(tag, MarginPopup):
tag.unmanage(manager)
tag.destroy()

0
otp/nametag/__init__.py Normal file
View file