class Menu(object): ''' ''' def __init__(self, parent): self._notify = DirectNotify().newCategory("Menu") self._notify.info("New Menu-Object created: %s" %(self)) #Font self.font = DynamicTextFont(FONT) self.font.setRenderMode(TextFont.RMSolid) self.KEY_DELAY = 0.15 self.player_buttonpressed = [] self._parent = parent self._players = parent.players self._devices = parent.devices taskMgr.add(self.fetchAnyKey, "fetchAnyKey") def showStartScreen(self): ''' the first screen with "press any Key" the device with the first key press will be the first player ''' self._notify.info("Initializing StartScreen") self.wii = [] #StartScreen Node self.startNode = NodePath("StartNode") self.startNode.reparentTo(render) self.startNode.setPos(-5,15,3) #Headline model headline = loader.loadModel("data/models/logo.egg") headline.setX(4.7) headline.setY(-4) headline.setZ(-2) headline.setP(90) headline.reparentTo(self.startNode) #Press any key text presskey = TextNode("PressAnyKey") presskey.setFont(self.font) presskey.setText(_("Press any key!!")) textNodePath = NodePath("PressAnyNode") textNodePath.attachNewNode(presskey) textNodePath.setPos(0,10,-9.5) textNodePath.reparentTo(self.startNode) #Show the start screen self.startNode.show() #LICHT plight = PointLight('plight') plight.setColor(VBase4(10, 10, 10, 1)) plnp = self.startNode.attachNewNode(plight) plnp.setPos(20, -800, 30) self.startNode.setLight(plnp) #Camera self.camera = base.makeCamera(base.win) self._notify.info("StarScreen initialized") # ----------------------------------------------------------------- def fetchAnyKey(self, task): ''' Return the first device with the first key stroke ''' for i in xrange(len(self._devices.devices)): if self._devices.devices[i].boost: #Kill Camera self.camera.node().setActive(False) #Kill Node self.startNode.removeNode() #Start the menu self.menu = MainMenu(self._parent, self.newGame, self._devices.devices[i], self._devices) self.menu.menuMain() return task.done return task.cont # ----------------------------------------------------------------- def newGame(self): ''' The select vehicle screen ''' base.enableParticles() self.countdown = COUNTDOWN_START #the countdown, when its over the game can be started self._notify.info("Initializing new game") #GlobPattern if we need a Panda Class self.vehicle_list = glob.glob("data/models/vehicles/*.egg") for index in range(len(self.vehicle_list)): self.vehicle_list[index] = Filename.fromOsSpecific(self.vehicle_list[index]).getFullpath() self._notify.debug("Vehicle list: %s" %(self.vehicle_list)) self.platform = loader.loadModel("data/models/platform.egg") #Loading-Text that gets displayed when loading a model text = TextNode("Loading") text.setFont(self.font) text.setText(_("Loading...")) text.setAlign(TextProperties.ACenter) self.loading = NodePath("LoadingNode") self.loading.attachNewNode(text) self.loading.setPos(0,0,2) #The countdown, its possible to start the game when its 0 text = TextNode("Countdown") text.setFont(self.font) text.setAlign(TextProperties.ACenter) text.setText(_(str(COUNTDOWN_START))) self.countdown_node = NodePath("Countdown") self.countdown_node.attachNewNode(text) self.countdown_node.setPos(0,0,4) #PreLoad the description that gets displayed when loading a model text = TextNode("name") text.setFont(self.font) text.setText(_("Loading...")) text.setAlign(TextProperties.ACenter) self.attributes = NodePath("AttributeNode") self.attributes.attachNewNode(text) self.unusedDevices = self._devices.devices[:] taskMgr.add(self.collectPlayer, "collectPlayer") #taskMgr.add(self.collectWii, "collectWii") self.screens = [] taskMgr.add(self.selectVehicle, "selectVehicle") self.color_red = Vec4(1,0,0,0) self.color_green = Vec4(0,1,0,0) self._notify.info("New game initialized") # ----------------------------------------------------------------- def selectVehicle(self, task): ''' The vehicle select and rotate task ''' #Set the countdown and hide, if > 3 self.countdown -= globalClock.getDt() if self.countdown <=0: self.countdown_node.getChild(0).node().setText(_("Go")) self.countdown_node.setColor(self.color_green) elif self.countdown <=3: self.countdown_node.getChild(0).node().setText(str(int(round((self.countdown+0.5))))) self.countdown_node.setColor(self.color_red) self.countdown_node.show() else: self.countdown_node.hide() heading = self.loading.getH() -(30 * globalClock.getDt()) self.loading.setH(heading) for player in self._players: if player.vehicle.model != None: player.vehicle.model.setH(heading) if self.player_buttonpressed[self._players.index(player)] < task.time and not player.vehicle.model_loading: if player.device.directions[0] < -0.8: self._parent.menuselect.play() self.countdown = COUNTDOWN_START self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY index = self.vehicle_list.index("data/models/vehicles/%s" %(player.vehicle.model.getName()))-1 self._notify.debug("Previous vehicle selected: %s" %(index)) player.vehicle.model_loading = True player.vehicle.model.hide() player.camera.camera.getParent().find("AttributeNode").hide()#Hide the attributes self.loading.instanceTo(player.camera.camera.getParent()) loader.loadModel(self.vehicle_list[index], callback = player.setVehicle) elif player.device.directions[0] > 0.8: self._parent.menuselect.play() self.countdown = COUNTDOWN_START self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY index = self.vehicle_list.index("data/models/vehicles/%s" %(player.vehicle.model.getName()))+1 self._notify.debug("Next vehicle selected: %s" %(index)) if index >= len(self.vehicle_list): index = 0 player.vehicle.model_loading = True player.vehicle.model.hide() player.camera.camera.getParent().find("AttributeNode").hide()#Hide the attributes self.loading.instanceTo(player.camera.camera.getParent()) loader.loadModel(self.vehicle_list[index], callback = player.setVehicle) if player.device.directions[1] > 0.8: self.countdown = COUNTDOWN_START self._parent.menuselect.play() self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY self._notify.debug("Next color selected") a = player.vehicle.model.findAllTextures() tex = a.findTexture(player.vehicle.model.getName()[:-4]) self.rotateHue(tex, 0.1) elif player.device.directions[1] < -0.8: self._parent.menuselect.play() self.countdown = COUNTDOWN_START self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY self._notify.debug("Next color selected") a = player.vehicle.model.findAllTextures() tex = a.findTexture(player.vehicle.model.getName()[:-4]) self.rotateHue(tex, -0.1) return task.cont # ----------------------------------------------------------------- def rotateHue(self, tex, value=0.1): ''' ''' img = PNMImage() tex.store(img) for y in xrange(img.getReadYSize()): for x in xrange(img.getReadXSize()): r, g, b = img.getXel(x,y) h, s, v = colorsys.rgb_to_hsv(r, g, b) h += value if h < 0: h += 360 r, g, b = colorsys.hsv_to_rgb(h, s, v) img.setXel(x,y,r,g,b) tex.load(img) # ----------------------------------------------------------------- def collectWii(self, task): ''' Collect wiimotes task ''' try: print 'Put Wiimote in discoverable mode now (press 1+2)...' wiimote = cwiid.Wiimote() self.wiimoteX.append(wiimote) print len(self.wiimoteX) except: pass return task.cont # ----------------------------------------------------------------- def collectPlayer(self, task): ''' Wait until all players are ready ''' if len(self._players) > 0 and self.player_buttonpressed[0] < task.time: if self._players[0].device.boost and self.countdown <= 0: loading = False for player in self._players: if player.vehicle.model_loading: loading = True break self._notify.debug("Loading vehicle: %s" %(loading)) if not loading: taskMgr.remove("selectVehicle") self.track = trackgen3d.Track3d(1000, 1800, 1600, 1200, 5)#len(self._players)) self.streetPath = render.attachNewNode(self.track.createRoadMesh()) #self.borderleftPath = render.attachNewNode(self.track.createBorderLeftMesh()) self.borderleftPath = render.attachNewNode(self.track.createBorderLeftMesh()) self.borderrightPath = render.attachNewNode(self.track.createBorderRightMesh()) self.borderleftcollisionPath = NodePath(self.track.createBorderLeftCollisionMesh()) self.borderrightcollisionPath = NodePath(self.track.createBorderRightCollisionMesh()) ##self.borderPath = render.attachNewNode(self.track.createBorderMesh()) textures = ["tube", "tube2", "street"] tex = textures[random.randint(0, len(textures)-1)] roadtex = loader.loadTexture('data/textures/'+tex+'.png') bordertex = loader.loadTexture('data/textures/border.png') self.streetPath.setTexture(roadtex) self.borderleftPath.setTexture(bordertex) self.borderrightPath.setTexture(bordertex) #self.streetPath = loader.loadModel('data/models/Street.egg') ##self.streetPath = loader.loadModel('data/models/Street.egg') #tex = loader.loadTexture('data/models/StreetTex.png') #self.nodePath.setTexture(tex) self._parent.startGame(self.streetPath,self.borderleftPath,self.borderrightPath, self.track.trackpoints, self.borderleftcollisionPath, self.borderrightcollisionPath) return task.done for device in self.unusedDevices: if device.boost: self.countdown = COUNTDOWN_START self.player_buttonpressed.append(0) self._parent.addPlayer(device) #Set the PlayerCam to the Vehicle select menu Node vehicleSelectNode = NodePath("VehicleSelectNode") self._players[-1].camera.camera.reparentTo(vehicleSelectNode) #Light, that casts shadows plight = Spotlight('plight') plight.setColor(VBase4(10.0, 10.0, 10.0, 1)) if (base.win.getGsg().getSupportsBasicShaders() != 0): pass ##plight.setShadowCaster(True, 2048, 2048)#enable shadows for this light ##TODO wegen Linux #Light plight.getLens().setFov(80) plnp = vehicleSelectNode.attachNewNode(plight) plnp.setPos(2, -10, 10) plnp.lookAt(0,0,0) vehicleSelectNode.setLight(plnp) ## vehicleSelectNode.setShaderAuto()#enable autoshader so we can use shadows #Light ambilight = AmbientLight('ambilight') ambilight.setColor(VBase4(0.2, 0.2, 0.2, 1)) vehicleSelectNode.setLight(vehicleSelectNode.attachNewNode(ambilight)) self.platform.instanceTo(vehicleSelectNode) #Load the platform #instance shown text self.countdown_node.instanceTo(vehicleSelectNode) #Instance the Countdown self.loading.instanceTo(vehicleSelectNode) #Show the Loading-Text self.attributes.copyTo(vehicleSelectNode).hide() self._players[-1].vehicle.model_loading = True #start loading the model loader.loadModel(self.vehicle_list[0], callback = self._players[-1].setVehicle) self._notify.debug("Loading initial vehicle: %s" %(self.vehicle_list[0])) self.unusedDevices.remove(device) self.player_buttonpressed[-1] = task.time + self.KEY_DELAY #Add the Skybox skybox = loader.loadModel("data/models/skybox.egg") t = Texture() t.load(PNMImage("data/textures/skybox_hangar.png")) skybox.setTexture(t) skybox.setBin("background", 1) skybox.setDepthWrite(0) skybox.setDepthTest(0) skybox.setLightOff() skybox.setScale(10000) skybox.reparentTo(vehicleSelectNode) for player in self._players: if self.player_buttonpressed[self._players.index(player)] < task.time: if player.device.use_item: self.countdown = COUNTDOWN_START self._notify.debug("Removing player: %s" %(player)) self.unusedDevices.append(player.device) self.player_buttonpressed.pop(self._players.index(player)) self._parent.removePlayer(player) return task.cont
def traverse(self, nodePath, dnaStorage): root = NodePath('signroot') head_root = NodePath('root') wantDecalTest = base.config.GetBool('want-sign-decal-test', False) x = 0 for i in range(len(self.text)): tn = TextNode("text") tn.setText(self.text[i]) tn.setTextColor(self.color) font = dnaStorage.findFont(self.code) if font == None: raise DNAError.DNAError('Font code %s not found.' % self.code) tn.setFont(font) if i == 0 and 'b' in self.flags: tn.setTextScale(1.5) np = root.attachNewNode(tn) np.setScale(self.scale) np.setDepthWrite(0) if i % 2: np.setPos(x + self.stumble, 0, self.stomp) np.setR(-self.wiggle) else: np.setPos(x - self.stumble, 0, self.stomp) np.setR(self.wiggle) x += tn.getWidth() * np.getSx() + self.kern for i in range(root.getNumChildren()): c = root.getChild(i) c.setX(c.getX() - x / 2.) if self.width and self.height: for i in range(root.getNumChildren()): node = root.getChild(i) A = (node.getX() / (self.height / 2.)) B = (self.indent * math.pi / 180.) theta = A + B d = node.getY() x = math.sin(theta) * (self.height / 2.) y = (math.cos(theta) - 1) * (self.height / 2.) radius = math.hypot(x, y) if radius != 0: j = (radius + d) / radius x *= j y *= j node.setPos(x, 0, y) node.setR(node, (theta * 180.) / math.pi) collection = root.findAllMatches("**/+TextNode") for i in range(collection.getNumPaths()): xnp = collection.getPath(i) np2 = xnp.getParent().attachNewNode(xnp.node().generate()) np2.setTransform(xnp.getTransform()) xnp.removeNode() _np = nodePath.attachNewNode(root.node()) _np.setPosHpr(self.pos, self.hpr) if wantDecalTest: root.setEffect(DecalEffect.make()) else: _np.setDepthOffset(50) self.traverseChildren(_np, dnaStorage) _np.flattenStrong()
class StaticGeometricModel(object): """ load an object as a static geometric model -> changing pos, rot, color, etc. are not allowed there is no extra elements for this model, thus is much faster author: weiwei date: 20190312 """ def __init__(self, initor=None, name="defaultname", btransparency=True, btwosided=False): """ :param initor: path type defined by os.path or trimesh or nodepath :param btransparency :param name """ if isinstance(initor, StaticGeometricModel): self._objpath = copy.deepcopy(initor.objpath) self._objtrm = copy.deepcopy(initor.objtrm) self._objpdnp = copy.deepcopy(initor.objpdnp) self._name = copy.deepcopy(initor.name) self._localframe = copy.deepcopy(initor.localframe) else: # make a grandma nodepath to separate decorations (-autoshader) and raw nodepath (+autoshader) self._name = name self._objpdnp = NodePath(name) if isinstance(initor, str): self._objpath = initor self._objtrm = da.trm.load(self._objpath) objpdnp_raw = da.trimesh_to_nodepath(self._objtrm, name='pdnp_raw') objpdnp_raw.reparentTo(self._objpdnp) elif isinstance(initor, da.trm.Trimesh): self._objpath = None self._objtrm = initor objpdnp_raw = da.trimesh_to_nodepath(self._objtrm) objpdnp_raw.reparentTo(self._objpdnp) elif isinstance(initor, o3d.geometry.PointCloud ): # TODO should pointcloud be pdnp or pdnp_raw self._objpath = None self._objtrm = da.trm.Trimesh(np.asarray(initor.points)) objpdnp_raw = da.nodepath_from_points(self._objtrm.vertices, name='pdnp_raw') objpdnp_raw.reparentTo(self._objpdnp) elif isinstance( initor, np.ndarray): # TODO should pointcloud be pdnp or pdnp_raw self._objpath = None if initor.shape[1] == 3: self._objtrm = da.trm.Trimesh(initor) objpdnp_raw = da.nodepath_from_points( self._objtrm.vertices) elif initor.shape[1] == 7: self._objtrm = da.trm.Trimesh(initor[:, :3]) objpdnp_raw = da.nodepath_from_points( self._objtrm.vertices, initor[:, 3:].tolist()) objpdnp_raw.setRenderMode(RenderModeAttrib.MPoint, 3) else: # TODO depth UV? raise NotImplementedError objpdnp_raw.reparentTo(self._objpdnp) elif isinstance(initor, o3d.geometry.TriangleMesh): self._objpath = None self._objtrm = da.trm.Trimesh( vertices=initor.vertices, faces=initor.triangles, face_normals=initor.triangle_normals) objpdnp_raw = da.trimesh_to_nodepath(self._objtrm, name='pdnp_raw') objpdnp_raw.reparentTo(self._objpdnp) elif isinstance(initor, NodePath): self._objpath = None self._objtrm = None # TODO nodepath to trimesh? objpdnp_raw = initor objpdnp_raw.reparentTo(self._objpdnp) else: self._objpath = None self._objtrm = None objpdnp_raw = NodePath("pdnp_raw") objpdnp_raw.reparentTo(self._objpdnp) if btransparency: self._objpdnp.setTransparency(TransparencyAttrib.MDual) if btwosided: self._objpdnp.getChild(0).setTwoSided(True) self._localframe = None @property def name(self): # read-only property return self._name @property def objpath(self): # read-only property return self._objpath @property def objpdnp(self): # read-only property return self._objpdnp @property def objpdnp_raw(self): # read-only property return self._objpdnp.getChild(0) @property def objtrm(self): # read-only property # 20210328 comment out, allow None # if self._objtrm is None: # raise ValueError("Only applicable to models with a trimesh!") return self._objtrm @property def localframe(self): # read-only property return self._localframe @property def volume(self): # read-only property if self._objtrm is None: raise ValueError("Only applicable to models with a trimesh!") return self._objtrm.volume def set_rgba(self, rgba): self._objpdnp.setColor(rgba[0], rgba[1], rgba[2], rgba[3]) def get_rgba(self): return da.pdv4_to_npv4( self._objpdnp.getColor()) # panda3d.core.LColor -> LBase4F def clear_rgba(self): self._objpdnp.clearColor() def set_scale(self, scale=[1, 1, 1]): self._objpdnp.setScale(scale[0], scale[1], scale[2]) self._objtrm.apply_scale(scale) def get_scale(self): return da.pdv3_to_npv3(self._objpdnp.getScale()) def set_vert_size(self, size=.005): self.objpdnp_raw.setRenderModeThickness(size * 1000) def attach_to(self, obj): if isinstance(obj, ShowBase): # for rendering to base.render self._objpdnp.reparentTo(obj.render) elif isinstance(obj, StaticGeometricModel ): # prepared for decorations like local frames self._objpdnp.reparentTo(obj.objpdnp) elif isinstance(obj, mc.ModelCollection): obj.add_gm(self) else: print( "Must be ShowBase, modeling.StaticGeometricModel, GeometricModel, CollisionModel, or CollisionModelCollection!" ) def detach(self): self._objpdnp.detachNode() def remove(self): self._objpdnp.removeNode() def show_localframe(self): self._localframe = gen_frame() self._localframe.attach_to(self) def unshow_localframe(self): if self._localframe is not None: self._localframe.remove() self._localframe = None def copy(self): return copy.deepcopy(self)
class Player(DirectObject): #using this to be our player #define tehings like health in here #have to tie the camera to this #game manager ->player ->camera as far as instantiating goes def __init__(self): #Color values self.red = 0 self.green = 1 self.blue = 1 self.oRed = 0 self.oGreen = 1 self.oBlue = 1 #Limits use of weapons on menus self.canUseWeapons = True #Current overheat value self.overHeat = 0 #Amount of overheat reduced per cycle self.overHeatCount = .1 #Flag for overheated weapons self.isOverHeated = False #Flag for ccritical health self.down = True #Create player node self.playerNode = NodePath('player') self.playerNode.reparentTo(render) self.playerNode.setPos(0,-30,30) self.playerNode.setScale(1.0) #Create player model and reparent it to playerNode self.playerModel = loader.loadModel("./resources/player") self.playerModel.reparentTo(self.playerNode) self.playerModel.setPos(0,0,2) #Create weapons and store them in a map self.rRifle = RecursionRifle(base.camera, len(base.projectileList)) self.mhBlunder = MHB(base.camera, len(base.projectileList)) self.kvDuals = KeyValue(base.camera, len(base.projectileList)) self.weaponMap = {1:self.rRifle, 2:self.mhBlunder, 3:self.kvDuals} self.curWeapon = 1 #Names of weapons for switcching images self.weaponNameMap = {1:"./resources/rrName.png", 2:"./resources/mhbName.png", 3:"./resources/kvdName.png"} #Load all weaponName images in so it doesn't stutter on swap self.weaponName = OnscreenImage(self.weaponNameMap[3]) self.weaponName.setTransparency(True) self.weaponName.setImage(self.weaponNameMap[2]) self.weaponName.setTransparency(True) self.weaponName.setImage(self.weaponNameMap[1]) self.weaponName.setTransparency(True) self.weaponName.reparentTo(render2d) #Hide weapons currently not in use self.mhBlunder.hide() self.kvDuals.hide() #Weapon controls self.accept("mouse1", self.fireWeapon) self.accept("mouse3", self.swapWeapon) #Display HUD self.hud = OnscreenImage("resources/hud.png") self.hud.setTransparency(True) self.hud.reparentTo(render2d) # define player health here # try not to re-create the player object, will alter reset these values # alernatively, dump player stats off in save file before recreating print "Mod:",base.damageMod self.maxEnergy = 100/base.damageMod self.curEnergy = self.maxEnergy self.accept("cnode", self.hit) self.accept("pickuphealth", self.energyUpgrade) #Load font for text nodes font = loader.loadFont("./resources/ni7seg.ttf") #Text node for health bar self.healthLable = TextNode('health field name') self.healthLable.setFont(font) self.healthLable.setText("Abstraction") self.textNodePath = aspect2d.attachNewNode(self.healthLable) self.textNodePath.setScale(0.05) self.healthLable.setAlign(TextNode.ACenter) self.textNodePath.setPos(0, 0, .68) self.healthLable.setTextColor(self.red, self.green, self.blue, 1) #TextNode for enemy counter self.enemiesLeft = TextNode('monsters to kill') self.enemiesLeft.setFont(font) self.enemiesLeft.setText(str(len(base.enemyList))) self.texnp = aspect2d.attachNewNode(self.enemiesLeft) self.texnp.setScale(.1) self.texnp.setPos(-1.68, 0, -.75) self.enemiesLeft.setTextColor(1, 1, 0, 1) #Health bar self.bar = DirectWaitBar(text = "", value = self.curEnergy, range = self.maxEnergy, pos = (0,.4,.95), barColor = (self.red, self.green, self.blue, 1)) self.bar.setScale(0.5) #Usage bar self.usageBar = DirectWaitBar(text = "", value = self.overHeat, range = 100, pos = (1.25, -.4, -.95), barColor =(1, 0, 0,1)) self.usageBar.setScale(0.5) #Initailize player collision self.createColision() #Add player tasks base.taskMgr.add(self.updateUsage, "usagePaint", taskChain='Gametasks') base.taskMgr.add(self.hFlicker, "hflicker", taskChain='GameTasks') base.taskMgr.add(self.updateCount, "Ecount", taskChain='GameTasks') base.taskMgr.add(CameraMovement(self.playerModel).cameraControl, "cameraControl", taskChain='GameTasks') base.taskMgr.add(self.overHeatTask, "overHeatTask") base.taskMgr.add(self.killFloor, "Kill Floor") #Deducts health and updates health bar def hit(self, damage): self.curEnergy = self.curEnergy-damage self.bar['value'] = self.curEnergy #If the player dies if self.curEnergy <= 0: #Hide player HUD elements self.hide() #Player can't use weapons while dead self.canUseWeapons = False #Request game over menu from fsm base.fsm.request('GameOver', 1) #Hides all visable elements attached to the player def hide(self): self.weaponMap[self.curWeapon].reticle.setScale(0) self.weaponMap[self.curWeapon].curScale = 0 self.weaponMap[self.curWeapon].step = False self.textNodePath.hide() self.texnp.hide() self.hud.setScale(0) self.weaponName.setScale(0) self.usageBar.hide() self.bar.hide() #Display all visable elements attached to the player def show(self): self.textNodePath.show() self.texnp.show() self.hud.setScale(1) self.weaponName.setScale(1) self.usageBar.show() self.bar.show() #Reset the reticle for the current weapon if self.curWeapon == 1: self.weaponMap[1].reticle.setScale(.025) self.weaponMap[1].curScale = .025 elif self.curWeapon == 2: self.weaponMap[2].reticle.setScale(.075) self.weaponMap[2].curScale = .075 else: self.weaponMap[3].reticle.setScale(.025) self.weaponMap[3].curScale = .025 # set the player health to a specific value def adjustHealth(self, value): self.curEnergy = value self.bar['value'] = self.curEnergy #Update enemy counter def updateCount(self, task): self.enemiesLeft.setText(str(len(base.enemyList))) return task.cont #Update usage bar and color def updateUsage(self, task): if self.curEnergy > 0: if self.overHeat < 50: self.usageBar['barColor'] = (.2, 1, .5, 1) elif self.overHeat >=50 and self.overHeat <70: self.usageBar['barColor'] = (1, 1, .2, 1) elif self.overHeat >= 70: self.usageBar['barColor'] = (1, 0, 0, 1) self.usageBar['value'] = self.overHeat if self.isOverHeated: self.usageBar['barColor'] = (1, 0, 0, 1) return task.cont #Swaps to next weapon in the map def swapWeapon(self): #If you're not in a menu if self.canUseWeapons: #Reset weapon delay self.weaponMap[self.curWeapon].resetWeapon #Change to next weapon and hide reticles if self.curWeapon == 1: self.weaponName.setImage(self.weaponNameMap[2]) self.weaponName.setTransparency(True) self.weaponMap[1].reticle.setScale(0) self.weaponMap[1].curScale = 0 self.weaponMap[1].step = False self.rRifle.hide() self.mhBlunder.show() self.curWeapon = 2 self.weaponMap[2].reticle.setScale(.075) self.weaponMap[2].curScale = .075 elif self.curWeapon == 2: self.weaponName.setImage(self.weaponNameMap[3]) self.weaponName.setTransparency(True) self.weaponMap[2].reticle.setScale(0) self.weaponMap[2].curScale = 0 self.weaponMap[2].step = False self.mhBlunder.hide() self.kvDuals.show() self.curWeapon = 3 self.weaponMap[3].reticle.setScale(.025) self.weaponMap[3].curScale = .025 elif self.curWeapon == 3: self.weaponName.setImage(self.weaponNameMap[1]) self.weaponName.setTransparency(True) self.weaponMap[3].reticle.setScale(0) self.weaponMap[3].curScale = 0 self.weaponMap[3].step = False self.kvDuals.hide() self.rRifle.show() self.curWeapon = 1 self.weaponMap[1].reticle.setScale(.025) self.weaponMap[1].curScale = .025 base.taskMgr.remove("weaponDelay") #Fires current weapon def fireWeapon(self): #If the player is not in a menu if self.canUseWeapons: #If there isn't a weapon delay task add one if base.taskMgr.hasTaskNamed("weaponDelay") == False: #If your not overheated if not self.isOverHeated: base.taskMgr.add(self.weaponMap[self.curWeapon].fire, "fire") #If you can shoot as defined by weapon delay elif self.weaponMap[self.curWeapon].canShoot() == True: #and if your not overheated if not self.isOverHeated: base.taskMgr.remove("weaponDelay") base.taskMgr.add(self.weaponMap[self.curWeapon].fire, "fire") #Handles weapon overheat def overHeatTask(self, task): #Every cycle decrement your overheat by the specified amount self.overHeat -= self.overHeatCount #If you exceed the limit if self.overHeat >= 100: #Increase cooldown amount and set overheated flag self.overHeatCount = .5 self.isOverHeated = True #If your are not overheated anymore reset not default cooldown values elif self.overHeat < 0: self.overHeatCount = .1 self.overHeat = 0 self.isOverHeated = False return task.cont #Initialize player collision def createColision(self): #Set up floor collision handler base.floor = CollisionHandlerGravity() base.floor.setGravity(9.8) #Create player collision node and add to traverser self.playerCollNodePath = self.initCollisionSphere(self.playerNode.getChild(0)) base.cTrav.addCollider(self.playerCollNodePath, base.pusher) base.pusher.addCollider(self.playerCollNodePath, self.playerNode) # Create Floor Ray - for gravity / floor floorCollRayPath = self.initCollisionRay(1,-1) base.floor.addCollider(floorCollRayPath, self.playerNode) base.cTrav.addCollider(floorCollRayPath, base.floor) floorCollRayPath.reparentTo(self.playerModel) #Initialize player collision sphere def initCollisionSphere(self, obj): # Create sphere and attach to player cNode = CollisionNode('player') cs = CollisionSphere(0, 0, 4, 2) cNodePath = obj.attachNewNode(CollisionNode('cnode')) cNodePath.node().addSolid(cs) cNodePath.show() return cNodePath #Attach player to a collision ray def initCollisionRay(self, originZ, dirZ): ray = CollisionRay(0,0,originZ,0,0,dirZ) collNode = CollisionNode('playerRay') collNode.addSolid(ray) collNode.setFromCollideMask(BitMask32.bit(1)) collNode.setIntoCollideMask(BitMask32.allOff()) collRayNP = self.playerNode.attachNewNode(collNode) collRayNP.show() return collRayNP #call this method when collide with a health upgrade def energyUpgrade(self): self.maxEnergy +=(10/base.damageMod) self.curEnergy = self.curEnergy+(10/base.damageMod) self.bar['range'] = self.maxEnergy self.adjustHealth(self.curEnergy) #Hurts player and resets the to the levels origin upon falliong in a pit. def killFloor(self, task): z = int(self.playerNode.getPos()[2]) if(z < -7): self.playerNode.setPos(0, 0, 6) #resets height self.playerModel.setPos(base.xPos, base.yPos, base.zPos) #resets position self.hit(10) return task.cont #Makes the health bar flicker when your health is critical def hFlicker(self, task): if self.curEnergy <=30: if self.down: self.red = self.red+0.1 self.blue = self.blue-.01 self.green = self.green-.01 else: self.red = self.red-0.1 self.blue = self.green+0.1 self.green = self.green+0.1 else: self.red = self.oRed self.blue = self.oBlue self.green = self.oGreen if self.red >=1: self.down = False if self.red <=0: self.down = True self.healthLable.setTextColor(self.red, self.green, self.blue, 1) self.bar['barColor']=(self.red, self.green, self.blue, 1) return task.cont #Resets players health and signifies they have restarted level after death def resetEnergy(self): self.canUseWeapons = True self.curEnergy = self.maxEnergy self.adjustHealth(self.curEnergy)
class CollisionChecker(object): """ A fast collision checker that allows maximum 32 collision pairs author: weiwei date: 20201214osaka """ def __init__(self, name="auto"): self.ctrav = CollisionTraverser() self.chan = CollisionHandlerQueue() self.np = NodePath(name) self.bitmask_list = [BitMask32(2**n) for n in range(31)] self._bitmask_ext = BitMask32( 2**31) # 31 is prepared for cd with external non-active objects self.all_cdelements = [ ] # a list of cdlnks or cdobjs for quick accessing the cd elements (cdlnks/cdobjs) def add_cdlnks(self, jlcobj, lnk_idlist): """ The collision node of the given links will be attached to self.np, but their collision bitmask will be cleared When the a robot_s is treated as an obstacle by another robot_s, the IntoCollideMask of its all_cdelements will be set to BitMask32(2**31), so that the other robot_s can compare its active_cdelements with the all_cdelements. :param jlcobj: :param lnk_idlist: :return: author: weiwei date: 20201216toyonaka """ for id in lnk_idlist: if jlcobj.lnks[id]['cdprimit_childid'] == -1: # first time add cdnp = jlcobj.lnks[id]['collision_model'].copy_cdnp_to( self.np, clearmask=True) self.ctrav.addCollider(cdnp, self.chan) self.all_cdelements.append(jlcobj.lnks[id]) jlcobj.lnks[id]['cdprimit_childid'] = len( self.all_cdelements) - 1 else: raise ValueError("The link is already added!") def set_active_cdlnks(self, activelist): """ The specified collision links will be used for collision detection with external obstacles :param activelist: essentially a from list like [jlchain.lnk0, jlchain.lnk1...] the correspondent tolist will be set online in cd functions TODO use all elements in self.all_cdnlks if None :return: author: weiwei date: 20201216toyonaka """ for cdlnk in activelist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the add_cdlnks function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) cdnp.node().setFromCollideMask(self._bitmask_ext) def set_cdpair(self, fromlist, intolist): """ The given collision pair will be used for self collision detection :param fromlist: [[bool, cdprimit_cache], ...] :param intolist: [[bool, cdprimit_cache], ...] :return: author: weiwei date: 20201215 """ if len(self.bitmask_list) == 0: raise ValueError("Too many collision pairs! Maximum: 29") allocated_bitmask = self.bitmask_list.pop() for cdlnk in fromlist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the addjlcobj function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_from_cdmask = cdnp.node().getFromCollideMask() new_from_cdmask = current_from_cdmask | allocated_bitmask cdnp.node().setFromCollideMask(new_from_cdmask) for cdlnk in intolist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the addjlcobj function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask | allocated_bitmask cdnp.node().setIntoCollideMask(new_into_cdmask) def add_cdobj(self, objcm, rel_pos, rel_rotmat, into_list): """ :return: cdobj_info, a dictionary that mimics a joint link; Besides that, there is an additional 'into_list' key to hold into_list to easily toggle off the bitmasks. """ cdobj_info = {} cdobj_info['collision_model'] = objcm # for reversed lookup cdobj_info['gl_pos'] = objcm.get_pos() cdobj_info['gl_rotmat'] = objcm.get_rotmat() cdobj_info['rel_pos'] = rel_pos cdobj_info['rel_rotmat'] = rel_rotmat cdobj_info['into_list'] = into_list cdnp = objcm.copy_cdnp_to(self.np, clearmask=True) cdnp.node().setFromCollideMask(self._bitmask_ext) # set active self.ctrav.addCollider(cdnp, self.chan) self.all_cdelements.append(cdobj_info) cdobj_info['cdprimit_childid'] = len(self.all_cdelements) - 1 self.set_cdpair([cdobj_info], into_list) return cdobj_info def delete_cdobj(self, cdobj_info): """ :param cdobj_info: an lnk-like object generated by self.add_objinhnd :param objcm: :return: """ self.all_cdelements.remove(cdobj_info) cdnp_to_delete = self.np.getChild(cdobj_info['cdprimit_childid']) self.ctrav.removeCollider(cdnp_to_delete) this_cdmask = cdnp_to_delete.node().getFromCollideMask() for cdlnk in cdobj_info['into_list']: cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask & ~this_cdmask cdnp.node().setIntoCollideMask(new_into_cdmask) cdnp_to_delete.detachNode() self.bitmask_list.append(this_cdmask) def is_collided(self, obstacle_list=[], otherrobot_list=[], toggle_contact_points=False): """ :param obstacle_list: staticgeometricmodel :param otherrobot_list: :return: """ for cdelement in self.all_cdelements: pos = cdelement['gl_pos'] rotmat = cdelement['gl_rotmat'] cdnp = self.np.getChild(cdelement['cdprimit_childid']) cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat)) # print(da.npv3mat3_to_pdmat4(pos, rotmat)) # print("From", cdnp.node().getFromCollideMask()) # print("Into", cdnp.node().getIntoCollideMask()) # print("xxxx colliders xxxx") # for collider in self.ctrav.getColliders(): # print(collider.getMat()) # print("From", collider.node().getFromCollideMask()) # print("Into", collider.node().getIntoCollideMask()) # attach obstacles obstacle_parent_list = [] for obstacle in obstacle_list: obstacle_parent_list.append(obstacle.objpdnp.getParent()) obstacle.objpdnp.reparentTo(self.np) # attach other robots for robot in otherrobot_list: for cdnp in robot.cc.np.getChildren(): current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask | self._bitmask_ext cdnp.node().setIntoCollideMask(new_into_cdmask) robot.cc.np.reparentTo(self.np) # collision check self.ctrav.traverse(self.np) # clear obstacles for i, obstacle in enumerate(obstacle_list): obstacle.objpdnp.reparentTo(obstacle_parent_list[i]) # clear other robots for robot in otherrobot_list: for cdnp in robot.cc.np.getChildren(): current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask & ~self._bitmask_ext cdnp.node().setIntoCollideMask(new_into_cdmask) robot.cc.np.detachNode() if self.chan.getNumEntries() > 0: collision_result = True else: collision_result = False if toggle_contact_points: contact_points = [ da.pdv3_to_npv3(cd_entry.getSurfacePoint(base.render)) for cd_entry in self.chan.getEntries() ] return collision_result, contact_points else: return collision_result def show_cdprimit(self): """ Copy the current nodepath to base.render to show collision states TODO: maintain a list to allow unshow :return: author: weiwei date: 20220404 """ # print("call show_cdprimit") snp_cpy = self.np.copyTo(base.render) for cdelement in self.all_cdelements: pos = cdelement['gl_pos'] rotmat = cdelement['gl_rotmat'] cdnp = snp_cpy.getChild(cdelement['cdprimit_childid']) cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat)) cdnp.show() def disable(self): """ clear pairs and nodepath :return: """ for cdelement in self.all_cdelements: cdelement['cdprimit_childid'] = -1 self.all_cdelements = [] for child in self.np.getChildren(): child.removeNode() self.bitmask_list = list(range(31))
class PopupMenu(DirectObject): ''' A class to create a popup or context menu. Features : [1] it's destroyed by pressing ESCAPE, or LMB/RMB click outside of it [2] menu item's command is executed by pressing ENTER/RETURN or SPACE when it's hilighted [3] you can use arrow UP/DOWN to navigate [4] separator lines [5] menu item image [6] menu item hotkey If there are more than 1 item using the same hotkey, those items will be hilighted in cycle when the hotkey is pressed. [7] shortcut key text at the right side of menu item [8] multiple lines item text [9] menu item can have sub menus [10] it's offscreen-proof, try to put your pointer next to screen edge or corner before creating it ''' grayImages = {} # storage of grayed images, # so the same image will be converted to grayscale only once def __init__(self, items, parent=None, buttonThrower=None, onDestroy=None, font=None, baselineOffset=.0, scale=.05, itemHeight=1., leftPad=.0, separatorHeight=.5, underscoreThickness=1, BGColor=(0, 0, 0, .7), BGBorderColor=(1, .85, .4, 1), separatorColor=(1, 1, 1, 1), frameColorHover=(1, .85, .4, 1), frameColorPress=(0, 1, 0, 1), textColorReady=(1, 1, 1, 1), textColorHover=(0, 0, 0, 1), textColorPress=(0, 0, 0, 1), textColorDisabled=(.5, .5, .5, 1), minZ=None, useMouseZ=True): ''' items : a collection of menu items Item format : ( 'Item text', 'path/to/image', command ) OR ( 'Item text', 'path/to/image', command, arg1,arg2,.... ) If you don't want to use an image, pass 0. To create disabled item, pass 0 for the command : ( 'Item text', 'path/to/image', 0 ) so, you can easily switch between enabled or disabled : ( 'Item text', 'path/to/image', command if commandEnabled else 0 ) OR ( 'Item text', 'path/to/image', (0,command)[commandEnabled] ) To create submenu, pass a sequence of submenu items for the command. To create disabled submenu, pass an empty sequence for the command. To enable hotkey, insert an underscore before the character, e.g. hotkey of 'Item te_xt' is 'x' key. To add shortcut key text at the right side of the item, append it at the end of the item text, separated by "more than" sign, e.g. 'Item text>Ctrl-T'. To insert separator line, pass 0 for the whole item. parent : where to attach the menu, defaults to aspect2d buttonThrower : button thrower whose thrown events are blocked temporarily when the menu is displayed. If not given, the default button thrower is used onDestroy : user function which will be called after the menu is fully destroyed font : text font baselineOffset : text's baseline Z offset scale : text scale itemHeight : spacing between items, defaults to 1 leftPad : blank space width before text separatorHeight : separator line height, relative to itemHeight underscoreThickness : underscore line thickness BGColor, BGBorderColor, separatorColor, frameColorHover, frameColorPress, textColorReady, textColorHover, textColorPress, textColorDisabled are some of the menu components' color minZ : minimum Z position to restrain menu's bottom from going offscreen (-1..1). If it's None, it will be set a little above the screen's bottom. ''' self.parent = parent if parent else aspect2d self.onDestroy = onDestroy self.BT = buttonThrower if buttonThrower else base.buttonThrowers[ 0].node() self.menu = NodePath('menu-%s' % id(self)) self.parentMenu = None self.submenu = None self.BTprefix = self.menu.getName() + '>' self.submenuCreationTaskName = 'createSubMenu-' + self.BTprefix self.submenuRemovalTaskName = 'removeSubMenu-' + self.BTprefix self.font = font if font else TextNode.getDefaultFont() self.baselineOffset = baselineOffset if isinstance(scale, (float, int)): scale = (scale, 1.0, scale) self.scale = scale self.itemHeight = itemHeight self.leftPad = leftPad self.separatorHeight = separatorHeight self.underscoreThickness = underscoreThickness self.BGColor = BGColor self.BGBorderColor = BGBorderColor self.separatorColor = separatorColor self.frameColorHover = frameColorHover self.frameColorPress = frameColorPress self.textColorReady = textColorReady self.textColorHover = textColorHover self.textColorPress = textColorPress self.textColorDisabled = textColorDisabled self.minZ = minZ self.mpos = Point2(base.mouseWatcherNode.getMouse()) self.itemCommand = [] self.hotkeys = {} self.numItems = 0 self.sel = -1 self.selByKey = False bgPad = self.bgPad = .0125 texMargin = self.font.getTextureMargin() * self.scale[0] * .25 b = DirectButton(parent=NodePath(''), text='^|g_', text_font=self.font, scale=self.scale) fr = b.node().getFrame() b.getParent().removeNode() baselineToCenter = (fr[2] + fr[3]) * self.scale[0] LH = (fr[3] - fr[2]) * self.itemHeight * self.scale[2] imageHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .85 arrowHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .5 baselineToTop = (fr[3] * self.itemHeight * self.scale[2] / LH) / (1. + self.baselineOffset) baselineToBot = LH / self.scale[2] - baselineToTop itemZcenter = (baselineToTop - baselineToBot) * .5 separatorHalfHeight = .5 * separatorHeight * LH LSseparator = LineSegs() LSseparator.setColor(.5, .5, .5, .2) arrowVtx = [ (0, itemZcenter), (-2 * arrowHalfHeight, itemZcenter + arrowHalfHeight), (-arrowHalfHeight, itemZcenter), (-2 * arrowHalfHeight, itemZcenter - arrowHalfHeight), ] tri = Triangulator() vdata = GeomVertexData('trig', GeomVertexFormat.getV3(), Geom.UHStatic) vwriter = GeomVertexWriter(vdata, 'vertex') for x, z in arrowVtx: vi = tri.addVertex(x, z) vwriter.addData3f(x, 0, z) tri.addPolygonVertex(vi) tri.triangulate() prim = GeomTriangles(Geom.UHStatic) for i in range(tri.getNumTriangles()): prim.addVertices(tri.getTriangleV0(i), tri.getTriangleV1(i), tri.getTriangleV2(i)) prim.closePrimitive() geom = Geom(vdata) geom.addPrimitive(prim) geomNode = GeomNode('arrow') geomNode.addGeom(geom) realArrow = NodePath(geomNode) z = -baselineToTop * self.scale[2] - bgPad maxWidth = .1 / self.scale[0] shortcutTextMaxWidth = 0 anyImage = False anyArrow = False anyShortcut = False arrows = [] shortcutTexts = [] loadPrcFileData('', 'text-flatten 0') for item in items: if item: t, imgPath, f = item[:3] haveSubmenu = type(f) in SEQUENCE_TYPES anyArrow |= haveSubmenu anyImage |= isinstance(imgPath, bool) or bool(imgPath) disabled = not len(f) if haveSubmenu else not callable(f) args = item[3:] underlinePos = t.find('_') t = t.replace('_', '') shortcutSepPos = t.find('>') if shortcutSepPos > -1: if haveSubmenu: print( "\nA SHORTCUT KEY POINTING TO A SUBMENU IS NON-SENSE, DON'T YOU AGREE ?" ) else: shortcutText = NodePath( OnscreenText( parent=self.menu, text=t[shortcutSepPos + 1:], font=self.font, scale=1, fg=(1, 1, 1, 1), align=TextNode.ARight, )) shortcutTextMaxWidth = max( shortcutTextMaxWidth, abs(shortcutText.getTightBounds()[0][0])) anyShortcut = True t = t[:shortcutSepPos] else: shortcutText = '' EoLcount = t.count('\n') arrowZpos = -self.font.getLineHeight() * EoLcount * .5 if disabled: b = NodePath( OnscreenText( parent=self.menu, text=t, font=self.font, scale=1, fg=textColorDisabled, align=TextNode.ALeft, )) # don't pass the scale and position to OnscreenText constructor, # to maintain correctness between the OnscreenText and DirectButton items # due to the new text generation implementation b.setScale(self.scale) b.setZ(z) maxWidth = max(maxWidth, b.getTightBounds()[1][0] / self.scale[0]) if shortcutText: shortcutText.reparentTo(b) shortcutText.setColor(Vec4(*textColorDisabled), 1) shortcutText.setZ(arrowZpos) shortcutTexts.append(shortcutText) else: b = DirectButton( parent=self.menu, text=t, text_font=self.font, scale=self.scale, pos=(0, 0, z), text_fg=textColorReady, # text color when mouse over text2_fg=textColorHover, # text color when pressed text1_fg=textColorHover if haveSubmenu else textColorPress, # framecolor when pressed frameColor=frameColorHover if haveSubmenu else frameColorPress, commandButtons=[DGG.LMB, DGG.RMB], command=(lambda: 0) if haveSubmenu else self.__runCommand, extraArgs=[] if haveSubmenu else [f, args], text_align=TextNode.ALeft, relief=DGG.FLAT, rolloverSound=0, clickSound=0, pressEffect=0) b.stateNodePath[2].setColor( *frameColorHover) # framecolor when mouse over b.stateNodePath[0].setColor(0, 0, 0, 0) # framecolor when ready bframe = Vec4(b.node().getFrame()) if EoLcount: bframe.setZ(EoLcount * 10) b['frameSize'] = bframe maxWidth = max(maxWidth, bframe[1]) if shortcutText: for snpi, col in ((0, textColorReady), (1, textColorPress), (2, textColorHover)): sct = shortcutText.copyTo(b.stateNodePath[snpi], sort=10) sct.setColor(Vec4(*col), 1) sct.setZ(arrowZpos) shortcutTexts.append(sct) shortcutText.removeNode() if isinstance(imgPath, bool): if imgPath: if disabled: fg = textColorDisabled else: fg = textColorReady tick = NodePath( OnscreenText( parent=b, text=u"\u2714", font=self.font, scale=1, fg=fg, align=TextNode.ALeft, )) tick.setX(-2 * imageHalfHeight - leftPad) elif imgPath: img = loader.loadTexture(imgPath, okMissing=True) if img is not None: if disabled: if imgPath in PopupMenu.grayImages: img = PopupMenu.grayImages[imgPath] else: pnm = PNMImage() img.store(pnm) pnm.makeGrayscale(.2, .2, .2) img = Texture() img.load(pnm) PopupMenu.grayImages[imgPath] = img img.setMinfilter(Texture.FTLinearMipmapLinear) img.setWrapU(Texture.WMClamp) img.setWrapV(Texture.WMClamp) CM = CardMaker('') CM.setFrame(-2 * imageHalfHeight - leftPad, -leftPad, itemZcenter - imageHalfHeight, itemZcenter + imageHalfHeight) imgCard = b.attachNewNode(CM.generate()) imgCard.setTexture(img) if underlinePos > -1: oneLineText = t[:underlinePos + 1] oneLineText = oneLineText[oneLineText.rfind('\n') + 1:] tn = TextNode('') tn.setFont(self.font) tn.setText(oneLineText) tnp = NodePath(tn.getInternalGeom()) underlineXend = tnp.getTightBounds()[1][0] tnp.removeNode() tn.setText(t[underlinePos]) tnp = NodePath(tn.getInternalGeom()) b3 = tnp.getTightBounds() underlineXstart = underlineXend - (b3[1] - b3[0])[0] tnp.removeNode() underlineZpos = -.7 * baselineToBot - self.font.getLineHeight( ) * t[:underlinePos].count('\n') LSunder = LineSegs() LSunder.setThickness(underscoreThickness) LSunder.moveTo(underlineXstart + texMargin, 0, underlineZpos) LSunder.drawTo(underlineXend - texMargin, 0, underlineZpos) if disabled: underline = b.attachNewNode(LSunder.create()) underline.setColor(Vec4(*textColorDisabled), 1) else: underline = b.stateNodePath[0].attachNewNode( LSunder.create()) underline.setColor(Vec4(*textColorReady), 1) underline.copyTo(b.stateNodePath[1], 10).setColor( Vec4(*textColorHover if haveSubmenu else textColorPress), 1) underline.copyTo(b.stateNodePath[2], 10).setColor(Vec4(*textColorHover), 1) hotkey = t[underlinePos].lower() if hotkey in self.hotkeys: self.hotkeys[hotkey].append(self.numItems) else: self.hotkeys[hotkey] = [self.numItems] self.accept(self.BTprefix + hotkey, self.__processHotkey, [hotkey]) self.accept(self.BTprefix + 'alt-' + hotkey, self.__processHotkey, [hotkey]) if haveSubmenu: if disabled: arrow = realArrow.instanceUnderNode(b, '') arrow.setColor(Vec4(*textColorDisabled), 1) arrow.setZ(arrowZpos) else: arrow = realArrow.instanceUnderNode( b.stateNodePath[0], 'r') arrow.setColor(Vec4(*textColorReady), 1) arrow.setZ(arrowZpos) arrPress = realArrow.instanceUnderNode( b.stateNodePath[1], 'p') arrPress.setColor(Vec4(*textColorHover), 1) arrPress.setZ(arrowZpos) arrHover = realArrow.instanceUnderNode( b.stateNodePath[2], 'h') arrHover.setColor(Vec4(*textColorHover), 1) arrHover.setZ(arrowZpos) # weird, if sort order is 0, it's obscured by the frame for a in (arrPress, arrHover): a.reparentTo(a.getParent(), sort=10) if not disabled: extraArgs = [self.numItems, f if haveSubmenu else 0] self.accept(DGG.ENTER + b.guiId, self.__hoverOnItem, extraArgs) self.accept(DGG.EXIT + b.guiId, self.__offItem) #~ self.itemCommand.append((None,0) if haveSubmenu else (f,args)) self.itemCommand.append((f, args)) if self.numItems == 0: self.firstButtonIdx = int(b.guiId[2:]) self.numItems += 1 z -= LH + self.font.getLineHeight() * self.scale[2] * EoLcount else: # SEPARATOR LINE z += LH - separatorHalfHeight - baselineToBot * self.scale[2] LSseparator.moveTo(0, 0, z) LSseparator.drawTo(self.scale[0] * .5, 0, z) LSseparator.drawTo(self.scale[0], 0, z) z -= separatorHalfHeight + baselineToTop * self.scale[2] maxWidth += 7 * arrowHalfHeight * ( anyArrow or anyShortcut) + .2 + shortcutTextMaxWidth arrowXpos = maxWidth - arrowHalfHeight realArrow.setX(arrowXpos) if anyImage: leftPad += 2 * imageHalfHeight + leftPad for sct in shortcutTexts: sct.setX(maxWidth - 2 * (arrowHalfHeight * anyArrow + .2)) for c in asList(self.menu.findAllMatches('**/DirectButton*')): numLines = c.node().getFrame()[2] c.node().setFrame( Vec4( -leftPad, maxWidth, -baselineToBot - (numLines * .1 * self.itemHeight if numLines >= 10 else 0), baselineToTop)) loadPrcFileData('', 'text-flatten 1') try: minZ = self.menu.getChild(0).getRelativePoint( b, Point3(0, 0, b.node().getFrame()[2]))[2] except: minZ = self.menu.getChild(0).getRelativePoint( self.menu, Point3( 0, 0, b.getTightBounds()[0][2]))[2] - baselineToBot * .5 try: top = self.menu.getChild(0).node().getFrame()[3] except: top = self.menu.getChild(0).getZ() + baselineToTop l, r, b, t = -leftPad - bgPad / self.scale[ 0], maxWidth + bgPad / self.scale[0], minZ - bgPad / self.scale[ 2], top + bgPad / self.scale[2] menuBG = DirectFrame(parent=self.menu.getChild(0), frameSize=(l, r, b, t), frameColor=BGColor, state=DGG.NORMAL, suppressMouse=1) menuBorder = self.menu.getChild(0).attachNewNode('border') borderVtx = ( (l, 0, b), (l, 0, .5 * (b + t)), (l, 0, t), (.5 * (l + r), 0, t), (r, 0, t), (r, 0, .5 * (b + t)), (r, 0, b), (.5 * (l + r), 0, b), (l, 0, b), ) LSborderBG = LineSegs() LSborderBG.setThickness(4) LSborderBG.setColor(0, 0, 0, .7) LSborderBG.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborderBG.drawTo(*v) # fills the gap at corners for v in range(0, 7, 2): LSborderBG.moveTo(*(borderVtx[v])) menuBorder.attachNewNode(LSborderBG.create()) LSborder = LineSegs() LSborder.setThickness(2) LSborder.setColor(*BGBorderColor) LSborder.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborder.drawTo(*v) menuBorder.attachNewNode(LSborder.create()) for v in range(1, 8, 2): LSborderBG.setVertexColor(v, Vec4(0, 0, 0, .1)) LSborder.setVertexColor(v, Vec4(.3, .3, .3, .5)) menuBorderB3 = menuBorder.getTightBounds() menuBorderDims = menuBorderB3[1] - menuBorderB3[0] menuBG.wrtReparentTo(self.menu, sort=-1) self.menu.reparentTo(self.parent) x = -menuBorderB3[0][0] * self.scale[0] for c in asList(self.menu.getChildren()): c.setX(x) self.maxWidth = maxWidth = menuBorderDims[0] self.height = menuBorderDims[2] maxWidthR2D = maxWidth * self.menu.getChild(0).getSx(render2d) separatorLines = self.menu.attachNewNode(LSseparator.create(), 10) separatorLines.setSx(maxWidth) for v in range(1, LSseparator.getNumVertices(), 3): LSseparator.setVertexColor(v, Vec4(*separatorColor)) x = clamp(-.98, .98 - maxWidthR2D, self.mpos[0] - maxWidthR2D * .5) minZ = (-.98 if self.minZ is None else self.minZ) z = clamp( minZ + menuBorderDims[2] * self.scale[2] * self.parent.getSz(render2d), .98, self.mpos[1] if useMouseZ else -1000) self.menu.setPos(render2d, x, 0, z) self.menu.setTransparency(1) self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix(self.BTprefix) self.accept(self.BTprefix + 'escape', self.destroy) for e in ('mouse1', 'mouse3'): self.accept(self.BTprefix + e, self.destroy, [True]) self.accept(self.BTprefix + 'arrow_down', self.__nextItem) self.accept(self.BTprefix + 'arrow_down-repeat', self.__nextItem) self.accept(self.BTprefix + 'arrow_up', self.__prevItem) self.accept(self.BTprefix + 'arrow_up-repeat', self.__prevItem) self.accept(self.BTprefix + 'enter', self.__runSelItemCommand) self.accept(self.BTprefix + 'space', self.__runSelItemCommand) def __offItem(self, crap): self.sel = -1 self.__cancelSubmenuCreation() def __hoverOnItem(self, idx, menu, crap): self.sel = idx self.__cancelSubmenuCreation() if self.BT.getPrefix()==self.BTprefix or \ (self.submenu and self.submenuIdx==idx): self.__cancelSubmenuRemoval() if menu: if not (self.submenu and self.submenuIdx == idx): #~ if self.selByKey: #~ self.selByKey=False #~ self.__createSubmenu(idx,menu) #~ else: taskMgr.doMethodLater(.3, self.__createSubmenu, self.submenuCreationTaskName, extraArgs=[idx, menu]) else: taskMgr.doMethodLater(.5, self.__removeSubmenu, self.submenuRemovalTaskName, extraArgs=[]) def __cancelSubmenuCreation(self): taskMgr.removeTasksMatching('createSubMenu-*') def __createSubmenu(self, idx, menu): self.__cancelSubmenuCreation() self.__removeSubmenu() self.submenu = PopupMenu(items=menu, parent=self.parent, buttonThrower=self.BT, font=self.font, baselineOffset=self.baselineOffset, scale=self.scale, itemHeight=self.itemHeight, leftPad=self.leftPad, separatorHeight=self.separatorHeight, underscoreThickness=self.underscoreThickness, BGColor=self.BGColor, BGBorderColor=self.BGBorderColor, separatorColor=self.separatorColor, frameColorHover=self.frameColorHover, frameColorPress=self.frameColorPress, textColorReady=self.textColorReady, textColorHover=self.textColorHover, textColorPress=self.textColorPress, textColorDisabled=self.textColorDisabled, minZ=self.minZ, useMouseZ=False) self.submenuIdx = idx self.submenu.parentMenu = self if self.menu.getBinName(): self.submenu.menu.setBin(self.menu.getBinName(), self.menu.getBinDrawOrder() + 1) sb3 = self.submenu.menu.getTightBounds() sb = sb3[1] - sb3[0] b3 = self.menu.getTightBounds() x = b3[1][0] if render2d.getRelativePoint(self.parent, Point3(x + sb[0], 0, 0))[0] > .98: x = b3[0][0] - sb[0] if render2d.getRelativePoint(self.parent, Point3(x, 0, 0))[0] < -.98: x = self.parent.getRelativePoint(render2d, Point3(-.98, 0, 0))[0] item = self.menu.find('**/*-pg%s' % (self.firstButtonIdx + idx)) z = self.parent.getRelativePoint( item, Point3(0, 0, item.node().getFrame()[3]))[2] + self.bgPad self.submenu.menu.setPos(x, 0, max(z, self.submenu.menu.getZ())) # self.submenu.menu.setPos(x,0,z) def __nextItem(self): if self.numItems: self.sel = clamp(0, self.numItems - 1, self.sel + 1) self.__putPointerAtItem() self.selByKey = True def __prevItem(self): if self.numItems: self.sel = clamp(0, self.numItems - 1, (self.sel - 1) if self.sel > -1 else self.numItems - 1) self.__putPointerAtItem() self.selByKey = True def __putPointerAtItem(self): item = self.menu.find('**/*-pg%s' % (self.firstButtonIdx + self.sel)) fr = item.node().getFrame() c = Point3(.5 * (fr[0] + fr[1]), 0, .5 * (fr[2] + fr[3])) cR2D = render2d.getRelativePoint(item, c) x, y = int(base.win.getXSize() * .5 * (cR2D[0] + 1)), int( base.win.getYSize() * .5 * (-cR2D[2] + 1)) if '__origmovePointer' in base.win.DtoolClassDict: base.win.DtoolClassDict['__origmovePointer'](base.win, 0, x, y) else: base.win.movePointer(0, x, y) def __processHotkey(self, hotkey): itemsIdx = self.hotkeys[hotkey] if len(itemsIdx) == 1 and type( self.itemCommand[itemsIdx[0]][0]) not in SEQUENCE_TYPES: self.__runCommand(*self.itemCommand[itemsIdx[0]]) else: if self.sel in itemsIdx: idx = itemsIdx.index(self.sel) + 1 idx %= len(itemsIdx) self.sel = itemsIdx[idx] else: self.sel = itemsIdx[0] self.selByKey = True # if it's already there, putting the pointer doesn't trigger the 'enter' # event, so just bypass it if not (self.submenu and self.submenuIdx==self.sel) and\ type(self.itemCommand[itemsIdx[0]][0]) in SEQUENCE_TYPES: self.__createSubmenu(self.sel, self.itemCommand[itemsIdx[0]][0]) self.__putPointerAtItem() def __doRunCommand(self, f, args): self.destroy(delParents=True) f(*args) def __runCommand(self, f, args): if callable(f): # must be done at next frame, so shortcut key event won't bleed to the scene taskMgr.doMethodLater(.01, self.__doRunCommand, 'run menu command', extraArgs=[f, args]) def __runSelItemCommand(self): if self.sel == -1: return self.__runCommand(*self.itemCommand[self.sel]) def __cancelSubmenuRemoval(self): taskMgr.removeTasksMatching('removeSubMenu-*') def __removeSubmenu(self): self.__cancelSubmenuRemoval() if self.submenu: self.submenu.destroy() def destroy(self, delParents=False): self.__cancelSubmenuCreation() self.__removeSubmenu() self.subMenu = None self.ignoreAll() self.menu.removeNode() # if self.origBTprefix.find('menu-')==-1: # taskMgr.step() self.BT.setPrefix(self.origBTprefix) messenger.send(self.BTprefix + 'destroyed') if delParents and self.parentMenu: parent = self.parentMenu while parent.parentMenu: parent = parent.parentMenu parent.destroy() if self.parentMenu: self.parentMenu.submenuIdx = None self.parentMenu = None if callable(self.onDestroy): self.onDestroy()