def __init__(self, playerName = None, threadedNet = True): dcFileNames = ['direct.dc', 'tagger.dc'] ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames, connectMethod = self.CM_NET, threadedNet = threadedNet) self.GameGlobalsId = 1000 self.zoneInterest = None self.visInterest = None self.avatarManager = self.generateGlobalObject(1001, 'TagAvatarManager') base.transitions.FadeModelName = 'models/fade' # Allow some time for other processes. This also allows time # each frame for the network thread to run. base.setSleep(0.01) # If we're using OpenGL, we can enable shaders. (DirectX # shader support is still kind of spotty, even for simple # shaders like these.) if base.pipe and base.pipe.getInterfaceName() == 'OpenGL': Globals.EnableShaders = True self.gotMusic = False self.__getMusic() # For the browse button. self.posterDefaultDir = Filename.getHomeDirectory().toOsSpecific() # Load a fun font to be the default text font. labelFont = loader.loadFont('models/amsterdam.ttf', okMissing = True) if labelFont: # Make a fuzzy halo behind the font so it looks kind of # airbrushy labelFont.setOutline(VBase4(0, 0, 0, 1), 2.0, 0.9) TextNode.setDefaultFont(labelFont) base.disableMouse() if base.mouseWatcher: mb = ModifierButtons() mb.addButton(KeyboardButton.control()) base.mouseWatcher.node().setModifierButtons(mb) base.buttonThrowers[0].node().setModifierButtons(mb) taskMgr.setupTaskChain('loadPoster', numThreads = 4, threadPriority = TPLow) #taskMgr.setupTaskChain('net', numThreads = 1, threadPriority = TPLow, frameSync = True) # Set up a text property called "tag" that renders using the # tag font, in white, with a shadow. This is used for # rendering the art-painting awards at the end of the round. tpMgr = TextPropertiesManager.getGlobalPtr() tp = TextProperties() tagFont = loader.loadFont('models/one8seven.ttf', okMissing = True) if tagFont: tp.setFont(tagFont) tp.setTextColor(1, 1, 1, 1) tp.setShadow(0.05, 0.05) tp.setTextScale(1.5) tpMgr.setProperties('tag', tp) # If we're running from the web, get the gameInfo block from # the HTML tokens. self.gameInfo = None if base.appRunner: gameInfoName = base.appRunner.getToken('gameInfo') if gameInfoName: self.gameInfo = base.appRunner.evalScript(gameInfoName, needsResponse = True) # Expose the changePoster() method. base.appRunner.main.changePoster = self.changePoster print "self.gameInfo = %s" % (self.gameInfo) # Also be prepared to update the web form with the table of # players and the local player's score. self.playerTable = None self.scoreTable = None if base.appRunner and base.appRunner.dom: self.playerTable = base.appRunner.dom.document.getElementById('playerTable') self.scoreTable = base.appRunner.dom.document.getElementById('scoreTable') print "self.playerTable = %s, scoreTable = %s" % (self.playerTable, self.scoreTable) self.playerList = PlayerList(self.playerTable) self.onscreenScoreLeft = None # When we join a game, we'll prefer to join *this* game. self.nextGameId = 0 self.chooseGameTask = None self.allGames = [] # No game, no avatar (yet). self.robot = None self.game = None self.player = None self.av = None self.avCell = None self.paintThing = None self.keyMap = {} for key in Globals.ControlKeys: self.keyMap[key] = False self.dlnp = render.attachNewNode(DirectionalLight('dlnp')) self.dlnp.node().setColor((0.8, 0.8, 0.8, 1)) render.setLight(self.dlnp) self.alnp = render.attachNewNode(AmbientLight('alnp')) self.alnp.node().setColor((0.2, 0.2, 0.2, 1)) render.setLight(self.alnp) if base.camera: self.dlnp.reparentTo(base.camera) # Set up the poster FSM to switch the poster modes. self.posterFSM = PosterFSM(base.appRunner) # A node to hold all avatars. self.avRoot = render.attachNewNode('avRoot') # The root of the maze. self.mazeRoot = render.attachNewNode('maze') if Globals.EnableShaders: #self.mazeRoot.setShaderAuto() s = loader.loadShader('models/nopaint_normal.sha') self.mazeRoot.setShader(s) self.mazeRoot.setShaderInput('alight0', self.alnp) self.mazeRoot.setShaderInput('dlight0', self.dlnp) # Initial poster data. self.posterData = ('', 0) cvar = ConfigVariableFilename('tag-poster', '') filename = cvar.getValue() if filename: self.readTagPoster(filename) # Choose a bright paint color. h = random.random() s = random.random() * 0.3 + 0.7 v = random.random() * 0.3 + 0.7 self.playerColor = self.hsv2rgb(h, s, v) # Get the game's version. self.gameVersion = getattr(self.gameInfo, 'server-version', None) if not self.gameVersion: self.gameVersion = base.config.GetString('server-version', 'tagger-dev') # Get the player's name. name = playerName if not name: name = getattr(self.gameInfo, 'name', None) if not name: name = base.config.GetString('player-name', '') if name: # Use the provided name. self.playerName = name self.startConnect() else: # Prompt the user. # Start with the wall model in the background. wall = loader.loadModel('models/wall') wall.reparentTo(base.camera) wall.setPos(0, 5, -2.5) wall.setScale(10) if Globals.EnableShaders: wall.setShaderAuto() self.nameWall = wall c = self.playerColor self.nameLabel = DirectLabel( text = 'Enter your street name:', text_align = TextNode.ALeft, text_fg = (c[0], c[1], c[2], 1), text_shadow = (0, 0, 0, 1), pos = (-0.9, 0, 0.45), relief = None, scale = 0.2) tagFont = loader.loadFont('models/one8seven.ttf') cardModel = loader.loadModel('models/nametag_card') cardTex = cardModel.findTexture('*') self.nameEntry = DirectEntry( pos = (-0.9, 0, 0.1), focus = True, relief = DGG.TEXTUREBORDER, frameColor = (c[0], c[1], c[2], 0.6), frameTexture = cardTex, borderWidth = (0.2, 0.2), pad = (0.0, -0.2), borderUvWidth = (0.1, 0.1), text_font = tagFont, width = 12, scale = 0.15, command = self.enteredName) self.nameButton = DirectButton( text = 'Enter game', frameColor = (c[0], c[1], c[2], 1), scale = 0.15, pos = (0, 0, -0.5), relief = DGG.RAISED, borderWidth = (0.05, 0.05), pad = (0.8, 0.3), command = self.enteredName)
def __init__(self, playerName = None, threadedNet = True): dcFileNames = ['direct.dc', 'tagger.dc'] ClientRepository.__init__(self, dcFileNames = dcFileNames, connectMethod = self.CM_NET, threadedNet = threadedNet) base.transitions.FadeModelName = 'models/fade' # Need at least 32 bits to receive big picture packets. self.setTcpHeaderSize(4) # Allow some time for other processes. This also allows time # each frame for the network thread to run. base.setSleep(0.01) # If we're using OpenGL, we can enable shaders. (DirectX # shader support is still kind of spotty, even for simple # shaders like these.) if base.pipe and base.pipe.getInterfaceName() == 'OpenGL': Globals.EnableShaders = True self.gotMusic = False self.__getMusic() # For the browse button. self.posterDefaultDir = Filename.getHomeDirectory().toOsSpecific() # Load a fun font to be the default text font. labelFont = loader.loadFont('models/amsterdam.ttf', okMissing = True) if labelFont: # Make a fuzzy halo behind the font so it looks kind of # airbrushy labelFont.setOutline(VBase4(0, 0, 0, 1), 2.0, 0.9) TextNode.setDefaultFont(labelFont) base.disableMouse() if base.mouseWatcher: mb = ModifierButtons() mb.addButton(KeyboardButton.control()) base.mouseWatcher.node().setModifierButtons(mb) base.buttonThrowers[0].node().setModifierButtons(mb) taskMgr.setupTaskChain('loadPoster', numThreads = 4, threadPriority = TPLow) #taskMgr.setupTaskChain('net', numThreads = 1, threadPriority = TPLow, frameSync = True) # Set up a text property called "tag" that renders using the # tag font, in white, with a shadow. This is used for # rendering the art-painting awards at the end of the round. tpMgr = TextPropertiesManager.getGlobalPtr() tp = TextProperties() tagFont = loader.loadFont('models/one8seven.ttf', okMissing = True) if tagFont: tp.setFont(tagFont) tp.setTextColor(1, 1, 1, 1) tp.setShadow(0.05, 0.05) tp.setTextScale(1.5) tpMgr.setProperties('tag', tp) # If we're running from the web, get the gameInfo block from # the HTML tokens. self.gameInfo = None if base.appRunner: gameInfoName = base.appRunner.getToken('gameInfo') if gameInfoName: self.gameInfo = base.appRunner.evalScript(gameInfoName, needsResponse = True) # Expose the changePoster() method. base.appRunner.main.changePoster = self.changePoster print "self.gameInfo = %s" % (self.gameInfo) # Also be prepared to update the web form with the table of # players and the local player's score. self.playerTable = None self.scoreTable = None if base.appRunner and base.appRunner.dom: self.playerTable = base.appRunner.dom.document.getElementById('playerTable') self.scoreTable = base.appRunner.dom.document.getElementById('scoreTable') print "self.playerTable = %s, scoreTable = %s" % (self.playerTable, self.scoreTable) self.playerList = PlayerList(self.playerTable) self.onscreenScoreLeft = None # When we join a game, we'll prefer to join *this* game. self.nextGameId = 0 self.chooseGameTask = None self.allGames = [] # No game, no avatar (yet). self.robot = None self.game = None self.player = None self.av = None self.avCell = None self.paintThing = None self.keyMap = {} for key in Globals.ControlKeys: self.keyMap[key] = False self.dlnp = render.attachNewNode(DirectionalLight('dlnp')) self.dlnp.node().setColor((0.8, 0.8, 0.8, 1)) render.setLight(self.dlnp) self.alnp = render.attachNewNode(AmbientLight('alnp')) self.alnp.node().setColor((0.2, 0.2, 0.2, 1)) render.setLight(self.alnp) if base.camera: self.dlnp.reparentTo(base.camera) # Set up the poster FSM to switch the poster modes. self.posterFSM = PosterFSM(base.appRunner) # A node to hold all avatars. self.avRoot = render.attachNewNode('avRoot') # The root of the maze. self.mazeRoot = render.attachNewNode('maze') if Globals.EnableShaders: #self.mazeRoot.setShaderAuto() s = loader.loadShader('models/nopaint_normal.sha') self.mazeRoot.setShader(s) self.mazeRoot.setShaderInput('alight0', self.alnp) self.mazeRoot.setShaderInput('dlight0', self.dlnp) # Initial poster data. self.posterData = ('', 0) cvar = ConfigVariableFilename('tag-poster', '') filename = cvar.getValue() if filename: self.readTagPoster(filename) # Choose a bright paint color. h = random.random() s = random.random() * 0.3 + 0.7 v = random.random() * 0.3 + 0.7 self.playerColor = self.hsv2rgb(h, s, v) # Get the player's name. name = playerName if not name: name = getattr(self.gameInfo, 'name', None) if not name: name = base.config.GetString('player-name', '') if name: # Use the provided name. self.playerName = name self.startConnect() else: # Prompt the user. # Start with the wall model in the background. wall = loader.loadModel('models/wall') wall.reparentTo(base.camera) wall.setPos(0, 5, -2.5) wall.setScale(10) if Globals.EnableShaders: wall.setShaderAuto() self.nameWall = wall c = self.playerColor self.nameLabel = DirectLabel( text = 'Enter your street name:', text_align = TextNode.ALeft, text_fg = (c[0], c[1], c[2], 1), text_shadow = (0, 0, 0, 1), pos = (-0.9, 0, 0.45), relief = None, scale = 0.2) tagFont = loader.loadFont('models/one8seven.ttf') cardModel = loader.loadModel('models/nametag_card') cardTex = cardModel.findTexture('*') self.nameEntry = DirectEntry( pos = (-0.9, 0, 0.1), focus = True, relief = DGG.TEXTUREBORDER, frameColor = (c[0], c[1], c[2], 0.6), frameTexture = cardTex, borderWidth = (0.2, 0.2), pad = (0.0, -0.2), borderUvWidth = (0.1, 0.1), text_font = tagFont, width = 12, scale = 0.15, command = self.enteredName) self.nameButton = DirectButton( text = 'Enter game', frameColor = (c[0], c[1], c[2], 1), scale = 0.15, pos = (0, 0, -0.5), relief = DGG.RAISED, borderWidth = (0.05, 0.05), pad = (0.8, 0.3), command = self.enteredName)
class TagClientRepository(ClientRepositoryBase): # Degrees per second of rotation rotateSpeed = 90 # Units per second of motion moveSpeed = 8 taskChain = 'net' def __init__(self, playerName = None, threadedNet = True): dcFileNames = ['direct.dc', 'tagger.dc'] ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames, connectMethod = self.CM_NET, threadedNet = threadedNet) self.GameGlobalsId = 1000 self.zoneInterest = None self.visInterest = None self.avatarManager = self.generateGlobalObject(1001, 'TagAvatarManager') base.transitions.FadeModelName = 'models/fade' # Allow some time for other processes. This also allows time # each frame for the network thread to run. base.setSleep(0.01) # If we're using OpenGL, we can enable shaders. (DirectX # shader support is still kind of spotty, even for simple # shaders like these.) if base.pipe and base.pipe.getInterfaceName() == 'OpenGL': Globals.EnableShaders = True self.gotMusic = False self.__getMusic() # For the browse button. self.posterDefaultDir = Filename.getHomeDirectory().toOsSpecific() # Load a fun font to be the default text font. labelFont = loader.loadFont('models/amsterdam.ttf', okMissing = True) if labelFont: # Make a fuzzy halo behind the font so it looks kind of # airbrushy labelFont.setOutline(VBase4(0, 0, 0, 1), 2.0, 0.9) TextNode.setDefaultFont(labelFont) base.disableMouse() if base.mouseWatcher: mb = ModifierButtons() mb.addButton(KeyboardButton.control()) base.mouseWatcher.node().setModifierButtons(mb) base.buttonThrowers[0].node().setModifierButtons(mb) taskMgr.setupTaskChain('loadPoster', numThreads = 4, threadPriority = TPLow) #taskMgr.setupTaskChain('net', numThreads = 1, threadPriority = TPLow, frameSync = True) # Set up a text property called "tag" that renders using the # tag font, in white, with a shadow. This is used for # rendering the art-painting awards at the end of the round. tpMgr = TextPropertiesManager.getGlobalPtr() tp = TextProperties() tagFont = loader.loadFont('models/one8seven.ttf', okMissing = True) if tagFont: tp.setFont(tagFont) tp.setTextColor(1, 1, 1, 1) tp.setShadow(0.05, 0.05) tp.setTextScale(1.5) tpMgr.setProperties('tag', tp) # If we're running from the web, get the gameInfo block from # the HTML tokens. self.gameInfo = None if base.appRunner: gameInfoName = base.appRunner.getToken('gameInfo') if gameInfoName: self.gameInfo = base.appRunner.evalScript(gameInfoName, needsResponse = True) # Expose the changePoster() method. base.appRunner.main.changePoster = self.changePoster print "self.gameInfo = %s" % (self.gameInfo) # Also be prepared to update the web form with the table of # players and the local player's score. self.playerTable = None self.scoreTable = None if base.appRunner and base.appRunner.dom: self.playerTable = base.appRunner.dom.document.getElementById('playerTable') self.scoreTable = base.appRunner.dom.document.getElementById('scoreTable') print "self.playerTable = %s, scoreTable = %s" % (self.playerTable, self.scoreTable) self.playerList = PlayerList(self.playerTable) self.onscreenScoreLeft = None # When we join a game, we'll prefer to join *this* game. self.nextGameId = 0 self.chooseGameTask = None self.allGames = [] # No game, no avatar (yet). self.robot = None self.game = None self.player = None self.av = None self.avCell = None self.paintThing = None self.keyMap = {} for key in Globals.ControlKeys: self.keyMap[key] = False self.dlnp = render.attachNewNode(DirectionalLight('dlnp')) self.dlnp.node().setColor((0.8, 0.8, 0.8, 1)) render.setLight(self.dlnp) self.alnp = render.attachNewNode(AmbientLight('alnp')) self.alnp.node().setColor((0.2, 0.2, 0.2, 1)) render.setLight(self.alnp) if base.camera: self.dlnp.reparentTo(base.camera) # Set up the poster FSM to switch the poster modes. self.posterFSM = PosterFSM(base.appRunner) # A node to hold all avatars. self.avRoot = render.attachNewNode('avRoot') # The root of the maze. self.mazeRoot = render.attachNewNode('maze') if Globals.EnableShaders: #self.mazeRoot.setShaderAuto() s = loader.loadShader('models/nopaint_normal.sha') self.mazeRoot.setShader(s) self.mazeRoot.setShaderInput('alight0', self.alnp) self.mazeRoot.setShaderInput('dlight0', self.dlnp) # Initial poster data. self.posterData = ('', 0) cvar = ConfigVariableFilename('tag-poster', '') filename = cvar.getValue() if filename: self.readTagPoster(filename) # Choose a bright paint color. h = random.random() s = random.random() * 0.3 + 0.7 v = random.random() * 0.3 + 0.7 self.playerColor = self.hsv2rgb(h, s, v) # Get the game's version. self.gameVersion = getattr(self.gameInfo, 'server-version', None) if not self.gameVersion: self.gameVersion = base.config.GetString('server-version', 'tagger-dev') # Get the player's name. name = playerName if not name: name = getattr(self.gameInfo, 'name', None) if not name: name = base.config.GetString('player-name', '') if name: # Use the provided name. self.playerName = name self.startConnect() else: # Prompt the user. # Start with the wall model in the background. wall = loader.loadModel('models/wall') wall.reparentTo(base.camera) wall.setPos(0, 5, -2.5) wall.setScale(10) if Globals.EnableShaders: wall.setShaderAuto() self.nameWall = wall c = self.playerColor self.nameLabel = DirectLabel( text = 'Enter your street name:', text_align = TextNode.ALeft, text_fg = (c[0], c[1], c[2], 1), text_shadow = (0, 0, 0, 1), pos = (-0.9, 0, 0.45), relief = None, scale = 0.2) tagFont = loader.loadFont('models/one8seven.ttf') cardModel = loader.loadModel('models/nametag_card') cardTex = cardModel.findTexture('*') self.nameEntry = DirectEntry( pos = (-0.9, 0, 0.1), focus = True, relief = DGG.TEXTUREBORDER, frameColor = (c[0], c[1], c[2], 0.6), frameTexture = cardTex, borderWidth = (0.2, 0.2), pad = (0.0, -0.2), borderUvWidth = (0.1, 0.1), text_font = tagFont, width = 12, scale = 0.15, command = self.enteredName) self.nameButton = DirectButton( text = 'Enter game', frameColor = (c[0], c[1], c[2], 1), scale = 0.15, pos = (0, 0, -0.5), relief = DGG.RAISED, borderWidth = (0.05, 0.05), pad = (0.8, 0.3), command = self.enteredName) def enteredName(self, name = None): """ The user has entered his/her nickname. Launch the game. """ self.playerName = self.nameEntry.get() if not self.playerName: # Disallow entering with no name. self.nameLabel['text'] = 'You must enter a name!' return self.nameLabel.destroy() self.nameEntry.destroy() self.nameButton.destroy() self.nameWall.removeNode() self.startConnect() def lostConnection(self): # This should be overridden by a derived class to handle an # unexpectedly lost connection to the gameserver. self.notify.warning("Lost connection to gameserver.") if self.robot: self.exit() cbMgr = CullBinManager.getGlobalPtr() cbMgr.addBin('gui-popup', cbMgr.BTUnsorted, 60) self.failureText = OnscreenText( 'Lost connection to gameserver.\nPress ESC to quit.', scale = 0.15, fg = (1, 0, 0, 1), shadow = (0, 0, 0, 1), pos = (0, 0.2)) self.failureText.setBin('gui-popup', 0) base.transitions.fadeScreen(alpha = 1) render.hide() self.ignore('escape') self.accept('escape', self.exit) self.accept('control-escape', self.exit) def exit(self): #if self.gotMusic: # self.musicTrackFilename.unlink() if self.isConnected(): self.sendDisconnect() self.disconnect() sys.exit() def startConnect(self): self.url = None if self.gameInfo and getattr(self.gameInfo, 'server_url', None): self.url = URLSpec(self.gameInfo.server_url) if not self.url: tcpPort = base.config.GetInt('server-port', Globals.ServerPort) hostname = base.config.GetString('server-host', Globals.ServerHost) if not hostname: hostname = 'localhost' self.url = URLSpec('g://%s:%s' % (hostname, tcpPort)) self.waitingText = OnscreenText( 'Connecting to %s.\nPress ESC to cancel.' % (self.url), scale = 0.1, fg = (1, 1, 1, 1), shadow = (0, 0, 0, 1)) self.connect([self.url], successCallback = self.connectSuccess, failureCallback = self.connectFailure) def escape(self): """ The user pressed escape. Exit the client. """ self.exit() def connectFailure(self, statusCode, statusString): self.waitingText.destroy() self.failureText = OnscreenText( 'Failed to connect to %s:\n%s.\nPress ESC to quit.' % (self.url, statusString), scale = 0.15, fg = (1, 0, 0, 1), shadow = (0, 0, 0, 1), pos = (0, 0.2)) def makeWaitingText(self): if self.waitingText: self.waitingText.destroy() self.waitingText = OnscreenText( 'Waiting for server.', scale = 0.1, fg = (1, 1, 1, 1), shadow = (0, 0, 0, 1)) def connectSuccess(self): """ Successfully connected. But we still can't really do anything until we send an CLIENT_HELLO message. """ self.makeWaitingText() dg = PyDatagram() dg.addUint16(CLIENT_HELLO) dg.addUint32(self.hashVal) dg.addString(self.gameVersion) self.send(dg) # Make sure we have interest in the TimeManager zone, so we # always see it even if we switch to another zone. #self.setInterestZones([1]) # We must wait for the TimeManager to be fully created and # synced before we can enter zone 2 and wait for the game # object. #self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady) def handleDatagram(self, di): msgType = self.getMsgType() if msgType == CLIENT_HELLO_RESP: self.handleHelloResp() elif msgType == CLIENT_OBJECT_SET_FIELD: self.handleUpdateField(di) elif msgType == CLIENT_ENTER_OBJECT_REQUIRED: self.handleGenerate(di) elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER: self.handleGenerate(di, True) elif msgType == CLIENT_DONE_INTEREST_RESP: self.handleInterestDoneMessage(di) elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OWNER: self.handleGenerateOwner(di) elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER: self.handleGenerateOwner(di, True) elif msgType == CLIENT_OBJECT_LEAVING: self.handleDelete(di) elif msgType == CLIENT_EJECT: self.handleGoGetLost(di) def handleHelloResp(self): self.startHeartbeat() self.acceptOnce('accessResponse', self.handleResponse) self.avatarManager.requestAccess() def sendHeartbeat(self): dg = PyDatagram() dg.addUint16(CLIENT_HEARTBEAT) self.send(dg) def sendDisconnect(self): print 'Sending disconnect messsage' dg = PyDatagram() dg.addUint16(CLIENT_DISCONNECT) self.send(dg) def handleGenerate(self, di, other = False): doId = di.getUint32() parentId = di.getUint32() zoneId = di.getUint32() classId = di.getUint16() dclass = self.dclassesByNumber[classId] dclass.startGenerate() if other: self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId) else: self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId) dclass.stopGenerate() print dclass.getName() def handleGenerateOwner(self, di, other = False): doId = di.getUint32() parentId = di.getUint32() zoneId = di.getUint32() classId = di.getUint16() dclass = self.dclassesByNumber[classId] dclass.startGenerate() # Copied from ClientRepositoryBase and make it support # generating only required fields. if doId in self.doId2ownerView: # ...it is in our dictionary. # Just update it. self.notify.error('duplicate owner generate for %s (%s)' % ( doId, dclass.getName())) distObj = self.doId2ownerView[doId] assert distObj.dclass == dclass distObj.generate() if other: distObj.updateRequiredOtherFields(dclass, di) else: distObj.updateRequiredFields(dclass, di) # updateRequiredOtherFields calls announceGenerate elif self.cacheOwner.contains(doId): # ...it is in the cache. # Pull it out of the cache: distObj = self.cacheOwner.retrieve(doId) assert distObj.dclass == dclass # put it in the dictionary: self.doId2ownerView[doId] = distObj # and update it. distObj.generate() if other: distObj.updateRequiredOtherFields(dclass, di) else: distObj.updateRequiredFields(dclass, di) # updateRequiredOtherFields calls announceGenerate else: # ...it is not in the dictionary or the cache. # Construct a new one classDef = dclass.getOwnerClassDef() if classDef == None: self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName())) distObj = classDef(self) distObj.dclass = dclass # Assign it an Id distObj.doId = doId # Put the new do in the dictionary self.doId2ownerView[doId] = distObj # Update the required fields distObj.generateInit() # Only called when constructed distObj.generate() if other: distObj.updateRequiredOtherFields(dclass, di) else: distObj.updateRequiredFields(dclass, di) # updateRequiredOtherFields calls announceGenerate dclass.stopGenerate() print dclass.getName() if distObj.dclass.getName() == 'TagPlayer': player = self.doId2do.get(doId) if player: self.localPlayerGenerated(player) else: self.acceptOnce('generate-%d' % doId, self.localPlayerGenerated, [player]) elif distObj.dclass.getName() == 'TagAvatar': av = self.doId2do.get(doId) if av: self.localAvatarGenerated(av) else: self.acceptOnce('generate-%d' % doId, self.localAvatarGenerated, [av]) def handleDelete(self, di): doId = di.getUint32() if doId in self.doId2do: obj = self.doId2do[doId] del self.doId2do[doId] obj.deleteOrDelay() def localPlayerGenerated(self, player): self.player = player self.player.setupLocalPlayer(self) self.player.setPoster(self.posterData) def locateAvatar(self, zoneId): if self.av: dg = PyDatagram() dg.addUint16(CLIENT_OBJECT_LOCATION) dg.addUint32(self.av.doId) dg.addUint32(self.timeManager.doId) dg.addUint32(zoneId) self.send(dg) def handleResponse(self, resp): if resp == 1: self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady) self.mgrInterest = self.addInterest(self.GameGlobalsId, 1, 'game manager') def syncReady(self): """ Now we've got the TimeManager manifested, and we're in sync with the server time. Now we can enter the world. Check to see if we've received our doIdBase yet. """ self.waitForGame() #if self.haveCreateAuthority(): # self.gotCreateReady() #else: # Not yet, keep waiting a bit longer. # self.accept(self.uniqueName('createReady'), self.gotCreateReady) def gotCreateReady(self): """ Ready to enter the world. Expand our interest to include zone 2, and wait for a TagGame to show up. """ if not self.haveCreateAuthority(): # Not ready yet. return self.ignore(self.uniqueName('createReady')) self.waitForGame() def joinGame(self, game): """ Now we're involved in a game. """ self.ignore(self.uniqueName('joinGame')) self.accept(self.uniqueName('enterMaze'), self.enterMaze) if self.chooseGameTask: taskMgr.remove(self.chooseGameTask) self.chooseGameTask = None self.game = game self.objInterest = self.addInterest(self.timeManager.doId, self.game.objZone, 'game objects') if not self.player: self.game.d_requestPlayer() # Manifest a player. The player always has our "base" doId. #self.player = TagPlayer(self, name = self.playerName, # color = self.playerColor, # gameId = self.game.doId) #self.createDistributedObject(distObj = self.player, zoneId = self.game.objZone, doId = self.doIdBase, reserveDoId = True) #self.player.setupLocalPlayer(self) # Set the saved poster data, and also transmit it to the AI. #self.player.setPoster(self.posterData) self.game.sendUpdate('setPoster', [self.posterData]) self.accept(self.uniqueName('resetGame'), self.resetGame) def resetGame(self): """ Exit the game and wait for a new one. """ taskMgr.remove('moveAvatar') self.ignoreAll() self.stopPaint() self.nextGameId = 0 if self.game: self.nextGameId = self.game.nextGameId self.game.cleanup() self.game = None #if self.av: # self.sendDeleteMsg(self.av.doId) # self.av = None #if self.player: # self.sendDeleteMsg(self.player.doId) # self.player = None self.waitForGame() def waitForGame(self): """ Wait for a game to be generated for us to join. """ self.makeWaitingText() self.accept(self.uniqueName('joinGame'), self.joinGame) self.gamesListInterest = self.addInterest(self.timeManager.doId, 2, 'games list') if self.chooseGameTask: taskMgr.remove(self.chooseGameTask) self.chooseGameTask = None if self.nextGameId: game = self.doId2do.get(self.nextGameId) if game: # We already have the game we're waiting for. game.d_requestJoin() # If we're waiting for a particular game, give it time to # show up. self.chooseGameTask = taskMgr.doMethodLater(15, self.__chooseGame, 'chooseGame') else: # Otherwise, take whatever we get. self.chooseGameTask = taskMgr.doMethodLater(1, self.__chooseGame, 'chooseGame') def __chooseGame(self, task): """ Selects one of the available games to join at random. """ self.chooseGameTask = None # Choose a game to join at random. availableGames = [] for game in self.allGames: if game.gameActive and not game.rejectedMe: availableGames.append(game) if not availableGames: # Hmm, no games to choose. Ask for a new one. self.timeManager.d_requestNewGame() self.waitForGame() return game = random.choice(availableGames) game.d_requestJoin() def enterMaze(self, maze): self.ignore(self.uniqueName('enterMaze')) # We've got a maze. self.maze = maze #self.setInterestZones([1, 2]) if self.waitingText: self.waitingText.destroy() self.waitingText = None # If we're not running in a web and don't have a web-based # score table, then create an onscreen score table. if not self.scoreTable: tnp = base.a2dTopRight.attachNewNode('onscreenScore') self.onscreenScoreLeft = TextNode('onscreenScoreLeft') self.onscreenScoreLeft.setShadow(0.05, 0.05) self.onscreenScoreRight = TextNode('onscreenScoreRight') self.onscreenScoreRight.setShadow(0.05, 0.05) self.onscreenScoreRight.setAlign(TextNode.ARight) l = tnp.attachNewNode(self.onscreenScoreLeft) l.setX(-12) r = tnp.attachNewNode(self.onscreenScoreRight) tnp.setScale(0.07) tnp.setPos(-0.1, 0, -0.1) self.player.setScore(0) # A CollisionTraverser to detect when the avatar hits walls. self.avTrav = CollisionTraverser('avTrav') self.avTrav.setRespectPrevTransform(True) # A separate CollisionTraverser to ensure the camera still has # a line-of-sight to the avatar. self.camTrav = CollisionTraverser('camTrav') self.camSeg = CollisionSegment((0, 0, 0), (0, 1, 0)) camSegNode = CollisionNode('camSeg') camSegNode.addSolid(self.camSeg) self.camSegNP = base.camera.attachNewNode(camSegNode) self.camSegNP.setCollideMask(BitMask32(0)) camSegNode.setFromCollideMask(Globals.WallBit) self.camSegHandler = CollisionHandlerEvent() self.camSegHandler.setInPattern('blockVis') self.camSegHandler.setOutPattern('unblockVis') self.camTrav.addCollider(self.camSegNP, self.camSegHandler) self.accept('blockVis', self.blockVis) self.accept('unblockVis', self.unblockVis) # And yet another CollisionTraverser to detect where the spray # paint is being applied. self.paintTrav = CollisionTraverser('paintTrav') self.paintRay = CollisionSegment((0, 0, 0), (0, 1, 0)) paintRayNode = CollisionNode('paintRay') paintRayNode.addSolid(self.paintRay) self.paintRayNP = base.cam.attachNewNode(paintRayNode) self.paintRayNP.setCollideMask(BitMask32(0)) paintRayNode.setFromCollideMask(Globals.WallBit | Globals.FloorBit | Globals.AvatarBit | Globals.SelfBit) self.paintRayQueue = CollisionHandlerQueue() self.paintTrav.addCollider(self.paintRayNP, self.paintRayQueue) #self.paintTrav.showCollisions(render) # A buffer for rendering the false-color avatar offscreen. self.avbuf = None if base.win: self.avbufTex = Texture('avbuf') self.avbuf = base.win.makeTextureBuffer('avbuf', 256, 256, self.avbufTex, True) cam = Camera('avbuf') cam.setLens(base.camNode.getLens()) self.avbufCam = base.cam.attachNewNode(cam) dr = self.avbuf.makeDisplayRegion() dr.setCamera(self.avbufCam) self.avbuf.setActive(False) self.avbuf.setClearColor((1, 0, 0, 1)) cam.setCameraMask(Globals.AvBufMask) base.camNode.setCameraMask(Globals.CamMask) # avbuf renders everything it sees with the gradient texture. tex = loader.loadTexture('models/gradient.png') np = NodePath('np') np.setTexture(tex, 100) np.setColor((1, 1, 1, 1), 100) np.setColorScaleOff(100) np.setTransparency(TransparencyAttrib.MNone, 100) np.setLightOff(100) cam.setInitialState(np.getState()) render.hide(Globals.AvBufMask) # Manifest an avatar for ourselves. if self.av: self.localAvatarGenerated(self.av) else: self.game.d_requestAvatar(self.player.doId) #self.av = TagAvatar(self, playerId = self.player.doId) #x = random.uniform(0, maze.xsize * Globals.MazeScale) #y = random.uniform(0, maze.ysize * Globals.MazeScale) #h = random.uniform(0, 360) #self.av.setPosHpr(x, y, 0, h, 0, 0) #self.createDistributedObject(distObj = self.av, zoneId = 2) #self.av.setupLocalAvatar(self) #self.player.b_setAvId(self.av.doId) def localAvatarGenerated(self, av): self.av = av x = random.uniform(0, self.maze.xsize * Globals.MazeScale) y = random.uniform(0, self.maze.ysize * Globals.MazeScale) h = random.uniform(0, 360) self.av.setPosHpr(x, y, 0, h, 0, 0) self.av.setupLocalAvatar(self) # The camera arm follows behind the avatar. self.cameraArmHinge = self.av.attachNewNode('cameraArmHinge') self.cameraArm = self.cameraArmHinge.attachNewNode('cameraArm') self.cameraArm.setPos(0, -10, 2) base.camera.setPosHpr(self.cameraArm, 0, 0, 0, 0, 0, 0) # Sound effect while painting. self.spraySfx = loader.loadSfx('models/spray_middle.ogg') if self.spraySfx: self.spraySfx.setLoop(True) # Listen for movement control keys. for key in Globals.ControlKeys: self.accept(key, self.setKey, [key, True]) self.accept('control-' + key, self.setKey, [key, True]) self.accept(key + '-up', self.setKey, [key, False]) # Holding mouse button 3, or control-mouse 1 (for macs), # activates camera-mouse mode. self.accept('mouse3', self.enableCameraMouse, [True]) self.accept('control-mouse1', self.enableCameraMouse, [True]) self.accept('mouse3-up', self.enableCameraMouse, [False]) self.accept('mouse1-up', self.enableCameraMouse, [False]) self.cameraMouseStart = None # Holding mouse button 1 paints. self.accept('mouse1', self.startPaint) # Now add the task that manages the avatar and camera # positions each frame. taskMgr.remove('moveAvatar') taskMgr.add(self.moveAvatar, 'moveAvatar') # Let the DistributedSmoothNode take care of broadcasting the # position updates several times a second. self.av.startPosHprBroadcast() def enableCameraMouse(self, enable): self.stopPaint() self.cameraMouseStart = None if enable and base.mouseWatcherNode.hasMouse(): # Record the starting position of the mouse pointer. mpos = base.mouseWatcherNode.getMouse() self.cameraMouseStart = (mpos.getX(), mpos.getY()) self.cameraMouseOrigHpr = self.cameraArmHinge.getHpr() # While in camera mouse mode, the camera is directly # parented to the camera arm. base.camera.wrtReparentTo(self.cameraArm) base.camera.setScale(1) else: # When no longer in camera mouse mode, the camera is # attached to render, and lags a little behind the camera # arm. base.camera.wrtReparentTo(render) base.camera.setScale(1) def blockVis(self, entry): """ A wall is between the camera and the avatar. Make it transparent. """ ## normal = entry.getSurfaceNormal(base.camera) ## if normal[1] > 0: ## # This wall is facing the wrong way; it doesn't count. ## return sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') cell = Cell.MazeCells.get((sx, sy), None) if cell: cell.setFade(dir, True) def unblockVis(self, entry): """ The wall is no longer between the camera and the avatar. Make it opaque again. """ sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') cell = Cell.MazeCells.get((sx, sy), None) if cell: cell.setFade(dir, False) def startPaint(self): taskMgr.add(self.doPaint, 'doPaint') if self.spraySfx: self.spraySfx.play() def stopPaint(self): taskMgr.remove('doPaint') if self.spraySfx: self.spraySfx.stop() if self.paintThing: self.paintThing.clearPaint() self.paintThing = None def doPaint(self, task): # The user is holding down the mouse button. Apply spray # paint to whatever surface is under the mouse. if not base.mouseWatcherNode.hasMouse(): # Mouse not in the window. return task.cont mpos = base.mouseWatcherNode.getMouse() self.paintRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) self.paintTrav.traverse(render) self.paintRayQueue.sortEntries() # Find the first entry with a normal pointing torwards the # camera. for entry in self.paintRayQueue.getEntries(): normal = entry.getSurfaceNormal(base.cam) if normal[1] > 0: # Facing the wrong way. continue paintType = entry.getIntoNodePath().getNetTag('paintType') if not paintType: # Not paintable. continue if paintType == 'cell': if self.__paintCell(entry): return task.cont elif paintType == 'avatar': if self.__paintAvatar(entry): return task.cont return task.cont def __paintCell(self, entry): """ Paints onto the cell wall, ceiling, or floor. Returns true on success, false on failure. """ # Check the wall's normal first; it must be pointed vaguely # towards the avatar to count. point = entry.getSurfacePoint(self.av.center) normal = entry.getSurfaceNormal(self.av.center) d = normal.dot(point) if d > 0: # The wall is facing the wrong way; it doesn't count. return False # The length of the point (treated as a vector), as seen from # the avatar, represents the distance to this point on the # wall. We attenuate the paint color based on this distance. distance = point.length() alpha = min(5.0 / (distance + 0.0001), 1.0) alpha = alpha * alpha # Now get the point in world coordinates, to determine the # paint location. point = entry.getSurfacePoint(render) point = Point3(point[0] / Globals.MazeScale, point[1] / Globals.MazeScale, point[2] / Globals.MazeZScale) sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') dx = point[0] - sx dy = point[1] - sy cell = Cell.MazeCells.get((sx, sy), None) if cell and not cell.getFade(dir): if cell is not self.paintThing: cell.clearPaint() colorBrush, whiteBrush = self.player.getBrushes(alpha) cell.paint(colorBrush, whiteBrush, dx, dy, point[2], dir) self.paintThing = cell return True return False def __paintAvatar(self, entry): """ Paints onto an avatar. Returns true on success, false on failure (because there are no avatar pixels under the mouse, for instance). """ # First, we have to render the avatar in its false-color # image, to determine which part of its texture is under the # mouse. if not self.avbuf: return False avId = entry.getIntoNodePath().getNetPythonTag('avId') av = self.doId2do.get(avId) if not av: return False mpos = base.mouseWatcherNode.getMouse() av.showThrough(Globals.AvBufMask) self.avbuf.setActive(True) base.graphicsEngine.renderFrame() av.show(Globals.AvBufMask) self.avbuf.setActive(False) # Now we have the rendered image in self.avbufTex. if not self.avbufTex.hasRamImage(): print "Weird, no image in avbufTex." return False p = PNMImage() self.avbufTex.store(p) ix = int((1 + mpos.getX()) * p.getXSize() * 0.5) iy = int((1 - mpos.getY()) * p.getYSize() * 0.5) x = 1 if ix >= 0 and ix < p.getXSize() and iy >= 0 and iy < p.getYSize(): s = p.getBlue(ix, iy) t = p.getGreen(ix, iy) x = p.getRed(ix, iy) if x > 0.5: # Off the avatar. return False # At point (s, t) on the avatar's map. # Get the distance to the avatar, for attenuation. distance = self.av.getDistance(av) alpha = min(5.0 / (distance + 0.0001), 1.0) alpha = alpha * alpha if av is not self.paintThing: av.clearPaint() colorBrush, whiteBrush = self.player.getBrushes(alpha) av.paint(colorBrush, whiteBrush, s, t) self.paintThing = av return True def changeAvZone(self, zoneId): """ Move the avatar into the indicated zone. """ # Move our avatar into the indicated zone self.setObjectZone(self.av, zoneId) def setKey(self, key, value): self.keyMap[key] = value def __determineCell(self, node): """ Returns the cell that the indicated node is positioned within, or None if it is not within any cell. """ sx = int(node.getX() / Globals.MazeScale) sy = int(node.getY() / Globals.MazeScale) cell = Cell.MazeCells.get((sx, sy), None) return cell def moveAvatar(self, task): """ Moves the avatar according to the WASD or arrow keys. """ # The amount of time elapsed since last frame. dt = globalClock.getDt() # Record the current position in case he goes off the grid. origPos = self.av.getPos() # The current position determines the active cell. self.avCell = self.__determineCell(self.av) moving = False if self.keyMap['arrow_left'] or self.keyMap['a']: self.av.setH(self.av.getH() + dt * Globals.TurnPerSecond) moving = True if self.keyMap['arrow_right'] or self.keyMap['d']: self.av.setH(self.av.getH() - dt * Globals.TurnPerSecond) moving = True if self.keyMap['arrow_down'] or self.keyMap['s']: self.av.setFluidY(self.av, -dt * Globals.BackwardPerSecond) moving = True if self.keyMap['arrow_up'] or self.keyMap['w']: self.av.setFluidY(self.av, dt * Globals.ForwardPerSecond) moving = True # Play the run animation if we're moving. if moving: self.av.setMoving(True) # No painting allowed while running. self.stopPaint() if not self.cameraMouseStart: # Also, ensure the camera arm is back where it # belongs, in case it was moved during camera-mouse # mode. self.cameraArmHinge.setHpr(0, 0, 0) else: self.av.setMoving(False) # Now check for collisions. if self.avCell: # Temporarily instance the current cell to avRoot, so we # can traverse the cell and the avatars at once. A little # bit hacky. tmp = self.avCell.root.instanceTo(self.avRoot) self.avTrav.traverse(self.avRoot) tmp.removeNode() # Check if he's still in the grid. newCell = self.__determineCell(self.av) if not newCell: # No good: reset position. self.av.setPos(origPos) newCell = self.avCell if self.cameraMouseStart and base.mouseWatcherNode.hasMouse(): # In camera-mouse mode, the camera arm can be swung around # to look at the avatar from different points of view. mpos = base.mouseWatcherNode.getMouse() dx = (mpos.getX() - self.cameraMouseStart[0]) * base.win.getXSize() dy = (mpos.getY() - self.cameraMouseStart[1]) * base.win.getYSize() self.cameraArmHinge.setHpr(-dx * Globals.CameraPerPixel + self.cameraMouseOrigHpr[0], dy * Globals.CameraPerPixel + self.cameraMouseOrigHpr[1], 0) # The camera chases the cameraArm. vec = base.camera.getPos(self.cameraArm) dist = vec.length() if dist > 0: vec /= dist # Move closer to the target point. dist = max(dist - dt * Globals.CameraPerSecond, 0) dist = min(dist, Globals.MaxCameraDistance) base.camera.setPos(self.cameraArm, vec * dist) # Ensure the camera is looking at our avatar, without tilting # downward. base.camera.headsUp(self.av) # Perform the line-of-sight check. to = base.camera.getRelativePoint(self.av, (0, 0, 1)) self.camSeg.setPointB(to) self.camTrav.traverse(render) return task.cont def hsv2rgb(self, h, s, v): """ Given hue, saturation, value, return (r, g, b). """ h *= 6 # convert [0..1] to [0..6] i = math.floor(h) f = h - i if not (int(i) & 1): f = 1 - f # if i is even m = v * (1 - s) n = v * (1 - s * f) if i == 6 or i == 0: return (v, n, m) elif i == 1: return (n, v, m) elif i == 2: return (m, v, n) elif i == 3: return (m, n, v) elif i == 4: return (n, m, v) elif i == 5: return (v, m, n) assert False def __getMusic(self): """ This starts the download task to get the music track from the server in the background. """ assert not self.gotMusic self.musicTrackFilename = 'models/' + Globals.MusicTrack self.gotMusic = True messenger.send('gotMusic') #self.musicTrackFilename = Filename.temporary('', '', '.ogg') #print "Music filename = %s" % (self.musicTrackFilename) #http = HTTPClient.getGlobalPtr() #ch = http.makeChannel(False) #ch.beginGetDocument(Globals.MusicTrackURL) #ch.downloadToFile(self.musicTrackFilename) #taskMgr.add(self.__getMusicTask, 'getMusicTask', # extraArgs = [ch], appendTask = True) def __getMusicTask(self, ch, task): assert not self.gotMusic if ch.run(): # Come back later. return task.cont # We're done! if not ch.isValid(): print "Unable to download %s: %s" % ( Globals.MusicTrackURL, ch.getStatusString()) self.musicTrackFilename.unlink() return task.done print "Successfully downloaded %s to %s." % (Globals.MusicTrackURL, self.musicTrackFilename) self.gotMusic = True messenger.send('gotMusic') return task.done def changePoster(self): """ Called by the web code when the "change poster" button is clicked. """ if self.player: self.posterFSM.request('Hang', self.player.doId) def readTagPoster(self, filename): """ Fill the initial poster data with the data in the indicated file. This is generally used only in development, or when a user wants to preload a poster via a config file. """ tex = loader.loadTexture(filename) if not tex or tex.getOrigFileYSize() == 0: print "Could not read tag-poster %s" % (filename) return aspect = float(tex.getOrigFileXSize()) / float(tex.getOrigFileYSize()) p = PNMImage() tex.store(p) xs = min(p.getXSize(), 256) ys = min(p.getYSize(), 256) p1 = PNMImage(xs, ys) p1.quickFilterFrom(p) strm = StringStream() ConfigVariableInt('jpeg-quality').setValue(65) p1.write(strm, 'jpg') data = strm.getData() self.posterData = (data, aspect)
class TagClientRepository(ClientRepository): # Degrees per second of rotation rotateSpeed = 90 # Units per second of motion moveSpeed = 8 taskChain = 'net' def __init__(self, playerName = None, threadedNet = True): dcFileNames = ['direct.dc', 'tagger.dc'] ClientRepository.__init__(self, dcFileNames = dcFileNames, connectMethod = self.CM_NET, threadedNet = threadedNet) base.transitions.FadeModelName = 'models/fade' # Need at least 32 bits to receive big picture packets. self.setTcpHeaderSize(4) # Allow some time for other processes. This also allows time # each frame for the network thread to run. base.setSleep(0.01) # If we're using OpenGL, we can enable shaders. (DirectX # shader support is still kind of spotty, even for simple # shaders like these.) if base.pipe and base.pipe.getInterfaceName() == 'OpenGL': Globals.EnableShaders = True self.gotMusic = False self.__getMusic() # For the browse button. self.posterDefaultDir = Filename.getHomeDirectory().toOsSpecific() # Load a fun font to be the default text font. labelFont = loader.loadFont('models/amsterdam.ttf', okMissing = True) if labelFont: # Make a fuzzy halo behind the font so it looks kind of # airbrushy labelFont.setOutline(VBase4(0, 0, 0, 1), 2.0, 0.9) TextNode.setDefaultFont(labelFont) base.disableMouse() if base.mouseWatcher: mb = ModifierButtons() mb.addButton(KeyboardButton.control()) base.mouseWatcher.node().setModifierButtons(mb) base.buttonThrowers[0].node().setModifierButtons(mb) taskMgr.setupTaskChain('loadPoster', numThreads = 4, threadPriority = TPLow) #taskMgr.setupTaskChain('net', numThreads = 1, threadPriority = TPLow, frameSync = True) # Set up a text property called "tag" that renders using the # tag font, in white, with a shadow. This is used for # rendering the art-painting awards at the end of the round. tpMgr = TextPropertiesManager.getGlobalPtr() tp = TextProperties() tagFont = loader.loadFont('models/one8seven.ttf', okMissing = True) if tagFont: tp.setFont(tagFont) tp.setTextColor(1, 1, 1, 1) tp.setShadow(0.05, 0.05) tp.setTextScale(1.5) tpMgr.setProperties('tag', tp) # If we're running from the web, get the gameInfo block from # the HTML tokens. self.gameInfo = None if base.appRunner: gameInfoName = base.appRunner.getToken('gameInfo') if gameInfoName: self.gameInfo = base.appRunner.evalScript(gameInfoName, needsResponse = True) # Expose the changePoster() method. base.appRunner.main.changePoster = self.changePoster print "self.gameInfo = %s" % (self.gameInfo) # Also be prepared to update the web form with the table of # players and the local player's score. self.playerTable = None self.scoreTable = None if base.appRunner and base.appRunner.dom: self.playerTable = base.appRunner.dom.document.getElementById('playerTable') self.scoreTable = base.appRunner.dom.document.getElementById('scoreTable') print "self.playerTable = %s, scoreTable = %s" % (self.playerTable, self.scoreTable) self.playerList = PlayerList(self.playerTable) self.onscreenScoreLeft = None # When we join a game, we'll prefer to join *this* game. self.nextGameId = 0 self.chooseGameTask = None self.allGames = [] # No game, no avatar (yet). self.robot = None self.game = None self.player = None self.av = None self.avCell = None self.paintThing = None self.keyMap = {} for key in Globals.ControlKeys: self.keyMap[key] = False self.dlnp = render.attachNewNode(DirectionalLight('dlnp')) self.dlnp.node().setColor((0.8, 0.8, 0.8, 1)) render.setLight(self.dlnp) self.alnp = render.attachNewNode(AmbientLight('alnp')) self.alnp.node().setColor((0.2, 0.2, 0.2, 1)) render.setLight(self.alnp) if base.camera: self.dlnp.reparentTo(base.camera) # Set up the poster FSM to switch the poster modes. self.posterFSM = PosterFSM(base.appRunner) # A node to hold all avatars. self.avRoot = render.attachNewNode('avRoot') # The root of the maze. self.mazeRoot = render.attachNewNode('maze') if Globals.EnableShaders: #self.mazeRoot.setShaderAuto() s = loader.loadShader('models/nopaint_normal.sha') self.mazeRoot.setShader(s) self.mazeRoot.setShaderInput('alight0', self.alnp) self.mazeRoot.setShaderInput('dlight0', self.dlnp) # Initial poster data. self.posterData = ('', 0) cvar = ConfigVariableFilename('tag-poster', '') filename = cvar.getValue() if filename: self.readTagPoster(filename) # Choose a bright paint color. h = random.random() s = random.random() * 0.3 + 0.7 v = random.random() * 0.3 + 0.7 self.playerColor = self.hsv2rgb(h, s, v) # Get the player's name. name = playerName if not name: name = getattr(self.gameInfo, 'name', None) if not name: name = base.config.GetString('player-name', '') if name: # Use the provided name. self.playerName = name self.startConnect() else: # Prompt the user. # Start with the wall model in the background. wall = loader.loadModel('models/wall') wall.reparentTo(base.camera) wall.setPos(0, 5, -2.5) wall.setScale(10) if Globals.EnableShaders: wall.setShaderAuto() self.nameWall = wall c = self.playerColor self.nameLabel = DirectLabel( text = 'Enter your street name:', text_align = TextNode.ALeft, text_fg = (c[0], c[1], c[2], 1), text_shadow = (0, 0, 0, 1), pos = (-0.9, 0, 0.45), relief = None, scale = 0.2) tagFont = loader.loadFont('models/one8seven.ttf') cardModel = loader.loadModel('models/nametag_card') cardTex = cardModel.findTexture('*') self.nameEntry = DirectEntry( pos = (-0.9, 0, 0.1), focus = True, relief = DGG.TEXTUREBORDER, frameColor = (c[0], c[1], c[2], 0.6), frameTexture = cardTex, borderWidth = (0.2, 0.2), pad = (0.0, -0.2), borderUvWidth = (0.1, 0.1), text_font = tagFont, width = 12, scale = 0.15, command = self.enteredName) self.nameButton = DirectButton( text = 'Enter game', frameColor = (c[0], c[1], c[2], 1), scale = 0.15, pos = (0, 0, -0.5), relief = DGG.RAISED, borderWidth = (0.05, 0.05), pad = (0.8, 0.3), command = self.enteredName) def enteredName(self, name = None): """ The user has entered his/her nickname. Launch the game. """ self.playerName = self.nameEntry.get() if not self.playerName: # Disallow entering with no name. self.nameLabel['text'] = 'You must enter a name!' return self.nameLabel.destroy() self.nameEntry.destroy() self.nameButton.destroy() self.nameWall.removeNode() self.startConnect() def lostConnection(self): # This should be overridden by a derived class to handle an # unexpectedly lost connection to the gameserver. self.notify.warning("Lost connection to gameserver.") if self.robot: self.exit() cbMgr = CullBinManager.getGlobalPtr() cbMgr.addBin('gui-popup', cbMgr.BTUnsorted, 60) self.failureText = OnscreenText( 'Lost connection to gameserver.\nPress ESC to quit.', scale = 0.15, fg = (1, 0, 0, 1), shadow = (0, 0, 0, 1), pos = (0, 0.2)) self.failureText.setBin('gui-popup', 0) base.transitions.fadeScreen(alpha = 1) render.hide() self.ignore('escape') self.accept('escape', self.exit) self.accept('control-escape', self.exit) def exit(self): if self.gotMusic: self.musicTrackFilename.unlink() sys.exit() def startConnect(self): self.url = None if self.gameInfo and getattr(self.gameInfo, 'server_url', None): self.url = URLSpec(self.gameInfo.server_url) if not self.url: tcpPort = base.config.GetInt('server-port', Globals.ServerPort) hostname = base.config.GetString('server-host', Globals.ServerHost) if not hostname: hostname = 'localhost' self.url = URLSpec('g://%s:%s' % (hostname, tcpPort)) self.waitingText = OnscreenText( 'Connecting to %s.\nPress ESC to cancel.' % (self.url), scale = 0.1, fg = (1, 1, 1, 1), shadow = (0, 0, 0, 1)) self.connect([self.url], successCallback = self.connectSuccess, failureCallback = self.connectFailure) def escape(self): """ The user pressed escape. Exit the client. """ self.exit() def connectFailure(self, statusCode, statusString): self.waitingText.destroy() self.failureText = OnscreenText( 'Failed to connect to %s:\n%s.\nPress ESC to quit.' % (self.url, statusString), scale = 0.15, fg = (1, 0, 0, 1), shadow = (0, 0, 0, 1), pos = (0, 0.2)) def makeWaitingText(self): if self.waitingText: self.waitingText.destroy() self.waitingText = OnscreenText( 'Waiting for server.', scale = 0.1, fg = (1, 1, 1, 1), shadow = (0, 0, 0, 1)) def connectSuccess(self): """ Successfully connected. But we still can't really do anything until we've got the doID range. """ self.makeWaitingText() # Make sure we have interest in the TimeManager zone, so we # always see it even if we switch to another zone. self.setInterestZones([1]) # We must wait for the TimeManager to be fully created and # synced before we can enter zone 2 and wait for the game # object. self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady) def syncReady(self): """ Now we've got the TimeManager manifested, and we're in sync with the server time. Now we can enter the world. Check to see if we've received our doIdBase yet. """ if self.haveCreateAuthority(): self.gotCreateReady() else: # Not yet, keep waiting a bit longer. self.accept(self.uniqueName('createReady'), self.gotCreateReady) def gotCreateReady(self): """ Ready to enter the world. Expand our interest to include zone 2, and wait for a TagGame to show up. """ if not self.haveCreateAuthority(): # Not ready yet. return self.ignore(self.uniqueName('createReady')) self.waitForGame() def joinGame(self, game): """ Now we're involved in a game. """ self.ignore(self.uniqueName('joinGame')) self.accept(self.uniqueName('enterMaze'), self.enterMaze) if self.chooseGameTask: taskMgr.remove(self.chooseGameTask) self.chooseGameTask = None self.game = game self.setInterestZones([1, 2, self.game.objZone]) # Manifest a player. The player always has our "base" doId. self.player = TagPlayer(self, name = self.playerName, color = self.playerColor, gameId = self.game.doId) self.createDistributedObject(distObj = self.player, zoneId = self.game.objZone, doId = self.doIdBase, reserveDoId = True) self.player.setupLocalPlayer(self) # Set the saved poster data, and also transmit it to the AI. self.player.setPoster(self.posterData) self.game.sendUpdate('setPoster', [self.posterData]) self.accept(self.uniqueName('resetGame'), self.resetGame) def resetGame(self): """ Exit the game and wait for a new one. """ taskMgr.remove('moveAvatar') self.ignoreAll() self.stopPaint() self.nextGameId = 0 if self.game: self.nextGameId = self.game.nextGameId self.game.cleanup() self.game = None if self.av: self.sendDeleteMsg(self.av.doId) self.av = None if self.player: self.sendDeleteMsg(self.player.doId) self.player = None self.waitForGame() def waitForGame(self): """ Wait for a game to be generated for us to join. """ self.makeWaitingText() self.accept(self.uniqueName('joinGame'), self.joinGame) self.setInterestZones([1, 2]) if self.chooseGameTask: taskMgr.remove(self.chooseGameTask) self.chooseGameTask = None if self.nextGameId: game = self.doId2do.get(self.nextGameId) if game: # We already have the game we're waiting for. game.d_requestJoin() # If we're waiting for a particular game, give it time to # show up. self.chooseGameTask = taskMgr.doMethodLater(15, self.__chooseGame, 'chooseGame') else: # Otherwise, take whatever we get. self.chooseGameTask = taskMgr.doMethodLater(1, self.__chooseGame, 'chooseGame') def __chooseGame(self, task): """ Selects one of the available games to join at random. """ self.chooseGameTask = None # Choose a game to join at random. availableGames = [] for game in self.allGames: if game.gameActive and not game.rejectedMe: availableGames.append(game) if not availableGames: # Hmm, no games to choose. Ask for a new one. self.timeManager.d_requestNewGame() self.waitForGame() return game = random.choice(availableGames) game.d_requestJoin() def enterMaze(self, maze): # We've got a maze. self.setInterestZones([1, 2]) if self.waitingText: self.waitingText.destroy() self.waitingText = None # If we're not running in a web and don't have a web-based # score table, then create an onscreen score table. if not self.scoreTable: tnp = base.a2dTopRight.attachNewNode('onscreenScore') self.onscreenScoreLeft = TextNode('onscreenScoreLeft') self.onscreenScoreLeft.setShadow(0.05, 0.05) self.onscreenScoreRight = TextNode('onscreenScoreRight') self.onscreenScoreRight.setShadow(0.05, 0.05) self.onscreenScoreRight.setAlign(TextNode.ARight) l = tnp.attachNewNode(self.onscreenScoreLeft) l.setX(-12) r = tnp.attachNewNode(self.onscreenScoreRight) tnp.setScale(0.07) tnp.setPos(-0.1, 0, -0.1) self.player.setScore(0) # A CollisionTraverser to detect when the avatar hits walls. self.avTrav = CollisionTraverser('avTrav') self.avTrav.setRespectPrevTransform(True) # A separate CollisionTraverser to ensure the camera still has # a line-of-sight to the avatar. self.camTrav = CollisionTraverser('camTrav') self.camSeg = CollisionSegment((0, 0, 0), (0, 1, 0)) camSegNode = CollisionNode('camSeg') camSegNode.addSolid(self.camSeg) self.camSegNP = base.camera.attachNewNode(camSegNode) self.camSegNP.setCollideMask(BitMask32(0)) camSegNode.setFromCollideMask(Globals.WallBit) self.camSegHandler = CollisionHandlerEvent() self.camSegHandler.setInPattern('blockVis') self.camSegHandler.setOutPattern('unblockVis') self.camTrav.addCollider(self.camSegNP, self.camSegHandler) self.accept('blockVis', self.blockVis) self.accept('unblockVis', self.unblockVis) # And yet another CollisionTraverser to detect where the spray # paint is being applied. self.paintTrav = CollisionTraverser('paintTrav') self.paintRay = CollisionSegment((0, 0, 0), (0, 1, 0)) paintRayNode = CollisionNode('paintRay') paintRayNode.addSolid(self.paintRay) self.paintRayNP = base.cam.attachNewNode(paintRayNode) self.paintRayNP.setCollideMask(BitMask32(0)) paintRayNode.setFromCollideMask(Globals.WallBit | Globals.FloorBit | Globals.AvatarBit | Globals.SelfBit) self.paintRayQueue = CollisionHandlerQueue() self.paintTrav.addCollider(self.paintRayNP, self.paintRayQueue) #self.paintTrav.showCollisions(render) # A buffer for rendering the false-color avatar offscreen. self.avbuf = None if base.win: self.avbufTex = Texture('avbuf') self.avbuf = base.win.makeTextureBuffer('avbuf', 256, 256, self.avbufTex, True) cam = Camera('avbuf') cam.setLens(base.camNode.getLens()) self.avbufCam = base.cam.attachNewNode(cam) dr = self.avbuf.makeDisplayRegion() dr.setCamera(self.avbufCam) self.avbuf.setActive(False) self.avbuf.setClearColor((1, 0, 0, 1)) cam.setCameraMask(Globals.AvBufMask) base.camNode.setCameraMask(Globals.CamMask) # avbuf renders everything it sees with the gradient texture. tex = loader.loadTexture('models/gradient.png') np = NodePath('np') np.setTexture(tex, 100) np.setColor((1, 1, 1, 1), 100) np.setColorScaleOff(100) np.setTransparency(TransparencyAttrib.MNone, 100) np.setLightOff(100) cam.setInitialState(np.getState()) render.hide(Globals.AvBufMask) # Manifest an avatar for ourselves. if self.av: self.sendDeleteMsg(self.av.doId) self.av = None self.av = TagAvatar(self, playerId = self.player.doId) x = random.uniform(0, maze.xsize * Globals.MazeScale) y = random.uniform(0, maze.ysize * Globals.MazeScale) h = random.uniform(0, 360) self.av.setPosHpr(x, y, 0, h, 0, 0) self.createDistributedObject(distObj = self.av, zoneId = 2) self.av.setupLocalAvatar(self) self.player.b_setAvId(self.av.doId) # The camera arm follows behind the avatar. self.cameraArmHinge = self.av.attachNewNode('cameraArmHinge') self.cameraArm = self.cameraArmHinge.attachNewNode('cameraArm') self.cameraArm.setPos(0, -10, 2) base.camera.setPosHpr(self.cameraArm, 0, 0, 0, 0, 0, 0) # Sound effect while painting. self.spraySfx = loader.loadSfx('models/spray_middle.ogg') if self.spraySfx: self.spraySfx.setLoop(True) # Listen for movement control keys. for key in Globals.ControlKeys: self.accept(key, self.setKey, [key, True]) self.accept('control-' + key, self.setKey, [key, True]) self.accept(key + '-up', self.setKey, [key, False]) # Holding mouse button 3, or control-mouse 1 (for macs), # activates camera-mouse mode. self.accept('mouse3', self.enableCameraMouse, [True]) self.accept('control-mouse1', self.enableCameraMouse, [True]) self.accept('mouse3-up', self.enableCameraMouse, [False]) self.accept('mouse1-up', self.enableCameraMouse, [False]) self.cameraMouseStart = None # Holding mouse button 1 paints. self.accept('mouse1', self.startPaint) # Now add the task that manages the avatar and camera # positions each frame. taskMgr.remove('moveAvatar') taskMgr.add(self.moveAvatar, 'moveAvatar') # Let the DistributedSmoothNode take care of broadcasting the # position updates several times a second. self.av.startPosHprBroadcast() def enableCameraMouse(self, enable): self.stopPaint() self.cameraMouseStart = None if enable and base.mouseWatcherNode.hasMouse(): # Record the starting position of the mouse pointer. mpos = base.mouseWatcherNode.getMouse() self.cameraMouseStart = (mpos.getX(), mpos.getY()) self.cameraMouseOrigHpr = self.cameraArmHinge.getHpr() # While in camera mouse mode, the camera is directly # parented to the camera arm. base.camera.wrtReparentTo(self.cameraArm) base.camera.setScale(1) else: # When no longer in camera mouse mode, the camera is # attached to render, and lags a little behind the camera # arm. base.camera.wrtReparentTo(render) base.camera.setScale(1) def blockVis(self, entry): """ A wall is between the camera and the avatar. Make it transparent. """ ## normal = entry.getSurfaceNormal(base.camera) ## if normal[1] > 0: ## # This wall is facing the wrong way; it doesn't count. ## return sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') cell = Cell.MazeCells.get((sx, sy), None) if cell: cell.setFade(dir, True) def unblockVis(self, entry): """ The wall is no longer between the camera and the avatar. Make it opaque again. """ sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') cell = Cell.MazeCells.get((sx, sy), None) if cell: cell.setFade(dir, False) def startPaint(self): taskMgr.add(self.doPaint, 'doPaint') if self.spraySfx: self.spraySfx.play() def stopPaint(self): taskMgr.remove('doPaint') if self.spraySfx: self.spraySfx.stop() if self.paintThing: self.paintThing.clearPaint() self.paintThing = None def doPaint(self, task): # The user is holding down the mouse button. Apply spray # paint to whatever surface is under the mouse. if not base.mouseWatcherNode.hasMouse(): # Mouse not in the window. return task.cont mpos = base.mouseWatcherNode.getMouse() self.paintRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) self.paintTrav.traverse(render) self.paintRayQueue.sortEntries() # Find the first entry with a normal pointing torwards the # camera. for entry in self.paintRayQueue.getEntries(): normal = entry.getSurfaceNormal(base.cam) if normal[1] > 0: # Facing the wrong way. continue paintType = entry.getIntoNodePath().getNetTag('paintType') if not paintType: # Not paintable. continue if paintType == 'cell': if self.__paintCell(entry): return task.cont elif paintType == 'avatar': if self.__paintAvatar(entry): return task.cont return task.cont def __paintCell(self, entry): """ Paints onto the cell wall, ceiling, or floor. Returns true on success, false on failure. """ # Check the wall's normal first; it must be pointed vaguely # towards the avatar to count. point = entry.getSurfacePoint(self.av.center) normal = entry.getSurfaceNormal(self.av.center) d = normal.dot(point) if d > 0: # The wall is facing the wrong way; it doesn't count. return False # The length of the point (treated as a vector), as seen from # the avatar, represents the distance to this point on the # wall. We attenuate the paint color based on this distance. distance = point.length() alpha = min(5.0 / (distance + 0.0001), 1.0) alpha = alpha * alpha # Now get the point in world coordinates, to determine the # paint location. point = entry.getSurfacePoint(render) point = Point3(point[0] / Globals.MazeScale, point[1] / Globals.MazeScale, point[2] / Globals.MazeZScale) sx, sy, dir = entry.getIntoNodePath().getNetPythonTag('step') dx = point[0] - sx dy = point[1] - sy cell = Cell.MazeCells.get((sx, sy), None) if cell and not cell.getFade(dir): if cell is not self.paintThing: cell.clearPaint() colorBrush, whiteBrush = self.player.getBrushes(alpha) cell.paint(colorBrush, whiteBrush, dx, dy, point[2], dir) self.paintThing = cell return True return False def __paintAvatar(self, entry): """ Paints onto an avatar. Returns true on success, false on failure (because there are no avatar pixels under the mouse, for instance). """ # First, we have to render the avatar in its false-color # image, to determine which part of its texture is under the # mouse. if not self.avbuf: return False avId = entry.getIntoNodePath().getNetPythonTag('avId') av = self.doId2do.get(avId) if not av: return False mpos = base.mouseWatcherNode.getMouse() av.showThrough(Globals.AvBufMask) self.avbuf.setActive(True) base.graphicsEngine.renderFrame() av.show(Globals.AvBufMask) self.avbuf.setActive(False) # Now we have the rendered image in self.avbufTex. if not self.avbufTex.hasRamImage(): print "Weird, no image in avbufTex." return False p = PNMImage() self.avbufTex.store(p) ix = int((1 + mpos.getX()) * p.getXSize() * 0.5) iy = int((1 - mpos.getY()) * p.getYSize() * 0.5) x = 1 if ix >= 0 and ix < p.getXSize() and iy >= 0 and iy < p.getYSize(): s = p.getBlue(ix, iy) t = p.getGreen(ix, iy) x = p.getRed(ix, iy) if x > 0.5: # Off the avatar. return False # At point (s, t) on the avatar's map. # Get the distance to the avatar, for attenuation. distance = self.av.getDistance(av) alpha = min(5.0 / (distance + 0.0001), 1.0) alpha = alpha * alpha if av is not self.paintThing: av.clearPaint() colorBrush, whiteBrush = self.player.getBrushes(alpha) av.paint(colorBrush, whiteBrush, s, t) self.paintThing = av return True def changeAvZone(self, zoneId): """ Move the avatar into the indicated zone. """ # Move our avatar into the indicated zone self.setObjectZone(self.av, zoneId) def setKey(self, key, value): self.keyMap[key] = value def __determineCell(self, node): """ Returns the cell that the indicated node is positioned within, or None if it is not within any cell. """ sx = int(node.getX() / Globals.MazeScale) sy = int(node.getY() / Globals.MazeScale) cell = Cell.MazeCells.get((sx, sy), None) return cell def moveAvatar(self, task): """ Moves the avatar according to the WASD or arrow keys. """ # The amount of time elapsed since last frame. dt = globalClock.getDt() # Record the current position in case he goes off the grid. origPos = self.av.getPos() # The current position determines the active cell. self.avCell = self.__determineCell(self.av) moving = False if self.keyMap['arrow_left'] or self.keyMap['a']: self.av.setH(self.av.getH() + dt * Globals.TurnPerSecond) moving = True if self.keyMap['arrow_right'] or self.keyMap['d']: self.av.setH(self.av.getH() - dt * Globals.TurnPerSecond) moving = True if self.keyMap['arrow_down'] or self.keyMap['s']: self.av.setFluidY(self.av, -dt * Globals.BackwardPerSecond) moving = True if self.keyMap['arrow_up'] or self.keyMap['w']: self.av.setFluidY(self.av, dt * Globals.ForwardPerSecond) moving = True # Play the run animation if we're moving. if moving: self.av.setMoving(True) # No painting allowed while running. self.stopPaint() if not self.cameraMouseStart: # Also, ensure the camera arm is back where it # belongs, in case it was moved during camera-mouse # mode. self.cameraArmHinge.setHpr(0, 0, 0) else: self.av.setMoving(False) # Now check for collisions. if self.avCell: # Temporarily instance the current cell to avRoot, so we # can traverse the cell and the avatars at once. A little # bit hacky. tmp = self.avCell.root.instanceTo(self.avRoot) self.avTrav.traverse(self.avRoot) tmp.removeNode() # Check if he's still in the grid. newCell = self.__determineCell(self.av) if not newCell: # No good: reset position. self.av.setPos(origPos) newCell = self.avCell if self.cameraMouseStart and base.mouseWatcherNode.hasMouse(): # In camera-mouse mode, the camera arm can be swung around # to look at the avatar from different points of view. mpos = base.mouseWatcherNode.getMouse() dx = (mpos.getX() - self.cameraMouseStart[0]) * base.win.getXSize() dy = (mpos.getY() - self.cameraMouseStart[1]) * base.win.getYSize() self.cameraArmHinge.setHpr(-dx * Globals.CameraPerPixel + self.cameraMouseOrigHpr[0], dy * Globals.CameraPerPixel + self.cameraMouseOrigHpr[1], 0) # The camera chases the cameraArm. vec = base.camera.getPos(self.cameraArm) dist = vec.length() if dist > 0: vec /= dist # Move closer to the target point. dist = max(dist - dt * Globals.CameraPerSecond, 0) dist = min(dist, Globals.MaxCameraDistance) base.camera.setPos(self.cameraArm, vec * dist) # Ensure the camera is looking at our avatar, without tilting # downward. base.camera.headsUp(self.av) # Perform the line-of-sight check. to = base.camera.getRelativePoint(self.av, (0, 0, 1)) self.camSeg.setPointB(to) self.camTrav.traverse(render) return task.cont def hsv2rgb(self, h, s, v): """ Given hue, saturation, value, return (r, g, b). """ h *= 6 # convert [0..1] to [0..6] i = math.floor(h) f = h - i if not (int(i) & 1): f = 1 - f # if i is even m = v * (1 - s) n = v * (1 - s * f) if i == 6 or i == 0: return (v, n, m) elif i == 1: return (n, v, m) elif i == 2: return (m, v, n) elif i == 3: return (m, n, v) elif i == 4: return (n, m, v) elif i == 5: return (v, m, n) assert False def __getMusic(self): """ This starts the download task to get the music track from the server in the background. """ assert not self.gotMusic self.musicTrackFilename = Filename.temporary('', '', '.ogg') print "Music filename = %s" % (self.musicTrackFilename) http = HTTPClient.getGlobalPtr() ch = http.makeChannel(False) ch.beginGetDocument(Globals.MusicTrackURL) ch.downloadToFile(self.musicTrackFilename) taskMgr.add(self.__getMusicTask, 'getMusicTask', extraArgs = [ch], appendTask = True) def __getMusicTask(self, ch, task): assert not self.gotMusic if ch.run(): # Come back later. return task.cont # We're done! if not ch.isValid(): print "Unable to download %s: %s" % ( Globals.MusicTrackURL, ch.getStatusString()) self.musicTrackFilename.unlink() return task.done print "Successfully downloaded %s to %s." % (Globals.MusicTrackURL, self.musicTrackFilename) self.gotMusic = True messenger.send('gotMusic') return task.done def changePoster(self): """ Called by the web code when the "change poster" button is clicked. """ if self.player: self.posterFSM.request('Hang', self.player.doId) def readTagPoster(self, filename): """ Fill the initial poster data with the data in the indicated file. This is generally used only in development, or when a user wants to preload a poster via a config file. """ tex = loader.loadTexture(filename) if not tex or tex.getOrigFileYSize() == 0: print "Could not read tag-poster %s" % (filename) return aspect = float(tex.getOrigFileXSize()) / float(tex.getOrigFileYSize()) p = PNMImage() tex.store(p) xs = min(p.getXSize(), 256) ys = min(p.getYSize(), 256) p1 = PNMImage(xs, ys) p1.quickFilterFrom(p) strm = StringStream() ConfigVariableInt('jpeg-quality').setValue(65) p1.write(strm, 'jpg') data = strm.getData() self.posterData = (data, aspect)