Exemple #1
0
class Scene(object):
    def __init__(self, sceneId=None):
        self.scene = NodePath('scene')
        if sceneId is not None:
            self.scene.setTag('scene-id', str(sceneId))
        self.sceneId = sceneId
        agents = self.scene.attachNewNode('agents')
        self.agents = [
            agents.attachNewNode('agent-0'),
        ]
        self.agents[0].setTag('agent-id', 'agent-0')

        self.worlds = dict()

    def getAllHouses(self):
        return self.scene.findAllMatches('**/house-*')

    def getAllRooms(self):
        return self.scene.findAllMatches('**/room-*')

    def getAllObjects(self):
        return self.scene.findAllMatches('**/object-*')

    def getAllAgents(self):
        return self.scene.findAllMatches('**/agents*')

    def getTotalNbHouses(self):
        nodepaths = self.getAllHouses()
        return len(nodepaths) if nodepaths else 0

    def getTotalNbRooms(self):
        nodepaths = self.getAllRooms()
        return len(nodepaths) if nodepaths else 0

    def getTotalNbObjects(self):
        nodepaths = self.getAllObjects()
        return len(nodepaths) if nodepaths else 0

    def getTotalNbAgents(self):
        nodepaths = self.getAllAgents()
        return len(nodepaths) if nodepaths else 0

    def hasGround(self):
        if self.scene.findAllMatches('**/ground*'):
            return True
        return False

    def __str__(self):
        return "Scene: %d houses, %d rooms, %d objects, %d agents" % (
            self.getTotalNbHouses(), self.getTotalNbRooms(),
            self.getTotalNbObjects(), self.getTotalNbAgents())

    __repr__ = __str__
Exemple #2
0
class Scene(object):
    def __init__(self):
        self.scene = NodePath('scene')
        agents = self.scene.attachNewNode('agents')
        self.agents = [
            agents.attachNewNode('agent-0'),
        ]
        self.worlds = dict()

    def getTotalNbHouses(self):
        nodepaths = self.scene.findAllMatches('**/house*')
        if nodepaths is not None:
            count = len(nodepaths)
        else:
            count = 0
        return count

    def getTotalNbRooms(self):
        nodepaths = self.scene.findAllMatches('**/room*')
        if nodepaths is not None:
            count = len(nodepaths)
        else:
            count = 0
        return count

    def getTotalNbObjects(self):
        nodepaths = self.scene.findAllMatches('**/object*')
        if nodepaths is not None:
            count = len(nodepaths)
        else:
            count = 0
        return count

    def getTotalNbAgents(self):
        nodepaths = self.scene.findAllMatches('**/agents/*')
        if nodepaths is not None:
            count = len(nodepaths)
        else:
            count = 0
        return count

    def __str__(self):
        return "Scene: %d houses, %d rooms, %d objects, %d agents" % (
            self.getTotalNbHouses(), self.getTotalNbRooms(),
            self.getTotalNbObjects(), self.getTotalNbAgents())

    __repr__ = __str__
class CogdoFlyingLevelQuadrant:
    notify = directNotify.newCategory('CogdoFlyingLevelQuadrant')

    def __init__(self, serialNum, model, level, parent):
        self.serialNum = serialNum
        self._model = model
        self._level = level
        self._root = NodePath('Quadrant' + `serialNum`)
        self._model.reparentTo(self._root)
        self._root.reparentTo(parent)
        self._visible = True
        self.platforms = {}
        self.gatherables = []
        self.obstacles = []
        self._playing = False
        self._obstaclesRoot = NodePath('obstacles')
        self._obstaclesRoot.reparentTo(self._root)
        self._initObstacles(self._obstaclesRoot)
        self._gatherablesRoot = NodePath('gatherables')
        self._gatherablesRoot.reparentTo(self._root)
        self._initGatherables(self._gatherablesRoot)
        self._platformsRoot = NodePath('platforms')
        self._platformsRoot.reparentTo(self._model)
        self._initPlatforms(self._platformsRoot)
        self._optimize()
        self.place()

    def _optimize(self):
        lightCones = NodePath('lightCones')
        for np in self._platformsRoot.findAllMatches('**/*lightCone*'):
            np.wrtReparentTo(lightCones)

        lightCones.reparentTo(self._model)
        node = self._model.find('**/ducts')
        if not node.isEmpty():
            node.flattenStrong()
            for np in node.getChildren():
                np.wrtReparentTo(self._model)

        node = self._model.find('**/nests')
        if not node.isEmpty():
            for np in node.getChildren():
                np.flattenStrong()
                np.wrtReparentTo(self._model)

        for np in self._model.findAllMatches('**/*LayerStack*'):
            np.wrtReparentTo(self._model)

        for np in self._model.find('**/static').getChildren():
            np.wrtReparentTo(self._model)

        self._model.flattenMedium()

    def _initPlatforms(self, parent):
        platformModels = self._model.findAllMatches('**/%s' % Globals.Level.PlatformName)
        for platformModel in platformModels:
            platform = CogdoFlyingPlatform(platformModel, parent=parent)
            self.platforms[platform.getName()] = platform

    def _destroyPlatforms(self):
        for platform in self.platforms.values():
            platform.destroy()

        del self.platforms

    def _initGatherables(self, parent):
        self.generateGatherables(self._model, parent=parent)
        if Globals.Level.SpawnLaffPowerupsInNests:
            self.generateNestPowerups(self._model, parent=parent)

    def generateNestPowerups(self, gatherableModel, parent):
        nests = gatherableModel.findAllMatches('**/%s;+s' % Globals.Level.LegalEagleNestName)
        for nest in nests:
            offset = Globals.Level.LaffPowerupNestOffset
            pickup = self._level.gatherableFactory.createPowerup(Globals.Level.GatherableTypes.LaffPowerup)
            pickup.reparentTo(parent)
            pickup.setPos(parent, nest.getPos(parent) + offset)
            if Globals.Level.AddSparkleToPowerups:
                sparkles = self._level.gatherableFactory.createSparkles(Vec4(1, 1, 1, 1), Vec4(1, 1, 0, 1), 10.0)
                sparkles.reparentTo(pickup)
                sparkles.setPos(0, 0, 1)
                sparkles.start()
            self.gatherables.append(pickup)

    def generateGatherables(self, gatherableModel, parent = None, spread = Globals.Level.GatherablesDefaultSpread):
        parent = parent or self._root
        mopath = Mopath.Mopath(name='gatherables')

        def generateMemos():
            gatherPaths = gatherableModel.findAllMatches('**/%s' % Globals.Level.GatherablesPathName)
            for gatherPath in gatherPaths:
                mopath.loadNodePath(gatherPath)
                t = 0.0
                while t < mopath.getMaxT():
                    pickup = self._level.gatherableFactory.createMemo()
                    pickup.reparentTo(parent)
                    mopath.goTo(pickup, t)
                    self.gatherables.append(pickup)
                    t += spread

                gatherPath.removeNode()

            angleSpread = 360.0 / Globals.Level.NumMemosInRing
            gatherPaths = gatherableModel.findAllMatches('**/%s' % Globals.Level.GatherablesRingName)
            for gatherPath in gatherPaths:
                mopath.loadNodePath(gatherPath)
                t = 0.0
                while t < mopath.getMaxT():
                    angle = 0
                    r = 3
                    while angle < 360.0:
                        pickup = self._level.gatherableFactory.createMemo()
                        pickup.reparentTo(parent)
                        mopath.goTo(pickup, t)
                        pickup.setX(parent, pickup.getX() + r * math.cos(math.radians(angle)))
                        pickup.setZ(parent, pickup.getZ() + r * math.sin(math.radians(angle)))
                        self.gatherables.append(pickup)
                        angle += angleSpread

                    t += spread + 0.5

                gatherPath.removeNode()

        def generatePropellers():
            gatherables = gatherableModel.findAllMatches('**/%s' % Globals.Level.PropellerName)
            for gatherable in gatherables:
                pickup = self._level.gatherableFactory.createPropeller()
                pickup.reparentTo(gatherable.getParent())
                pickup.setPos(parent, gatherable.getPos(parent))
                self.gatherables.append(pickup)
                gatherable.removeNode()

        def generatePowerUps():
            for powerupType, locName in Globals.Level.PowerupType2Loc.iteritems():
                if powerupType == Globals.Level.GatherableTypes.LaffPowerup and Globals.Level.IgnoreLaffPowerups:
                    continue
                gatherables = gatherableModel.findAllMatches('**/%s' % locName)
                for gatherable in gatherables:
                    pickup = self._level.gatherableFactory.createPowerup(powerupType)
                    pickup.reparentTo(parent)
                    pickup.setPos(parent, gatherable.getPos(parent))
                    if Globals.Level.AddSparkleToPowerups:
                        sparkles = self._level.gatherableFactory.createSparkles(Vec4(1, 1, 1, 1), Vec4(1, 1, 0, 1), 10.0)
                        sparkles.reparentTo(pickup)
                        sparkles.setPos(0, 0, 1)
                        sparkles.start()
                    self.gatherables.append(pickup)
                    gatherable.removeNode()

        generateMemos()
        generatePropellers()
        generatePowerUps()

    def _initObstacles(self, parent):

        def initWhirlwinds():
            obstacles = self._root.findAllMatches('**/%s' % Globals.Level.WhirlwindName)
            for obstacleLoc in obstacles:
                motionPath = self._model.find('**/%s%s' % (obstacleLoc.getName(), Globals.Level.WhirlwindPathName))
                if motionPath.isEmpty():
                    motionPath = None
                obstacle = self._level.obstacleFactory.createWhirlwind(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, obstacleLoc.getPos(parent))
                self.obstacles.append(obstacle)
                obstacleLoc.removeNode()

            return

        def initStreamers():
            obstacles = self._model.findAllMatches('**/%s' % Globals.Level.StreamerName)
            for obstacleLoc in obstacles:
                obstacle = self._level.obstacleFactory.createFan()
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, obstacleLoc.getPos(parent))
                obstacle.model.setHpr(parent, obstacleLoc.getHpr(parent))
                obstacle.model.setScale(parent, obstacleLoc.getScale(parent))
                obstacle.setBlowDirection()
                if Globals.Level.AddParticlesToStreamers:
                    particles = self._level.obstacleFactory.createStreamerParticles(Vec4(1, 1, 1, 1), Vec4(1, 1, 1, 1), 10.0)
                    particles.reparentTo(obstacle.model)
                    particles.start()
                self.obstacles.append(obstacle)
                obstacleLoc.removeNode()

        def initWalkingMinions():
            motionPaths = self._model.findAllMatches('**/%s' % Globals.Level.MinionWalkingPathName)
            for motionPath in motionPaths:
                obstacle = self._level.obstacleFactory.createWalkingMinion(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, motionPath.getPos(parent))
                self.obstacles.append(obstacle)

        def initFlyingMinions():
            motionPaths = self._model.findAllMatches('**/%s' % Globals.Level.MinionFlyingPathName)
            for motionPath in motionPaths:
                obstacle = self._level.obstacleFactory.createFlyingMinion(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, motionPath.getPos(parent))
                self.obstacles.append(obstacle)

        initWhirlwinds()
        initStreamers()
        initWalkingMinions()
        initFlyingMinions()

    def place(self):
        self._root.setPos(0, self._level.convertQuadNumToY(self.serialNum), 0)

    def destroy(self):
        if self._visible:
            self.offstage()
        self._destroyPlatforms()
        for obstacle in self.obstacles:
            obstacle.destroy()

        for gatherable in self.gatherables:
            gatherable.destroy()

        self._root.removeNode()
        del self._root
        del self._gatherablesRoot
        del self._obstaclesRoot
        del self._platformsRoot
        del self._level

    def onstage(self, elapsedTime = 0.0):
        if self._visible:
            return
        self._root.unstash()
        for obstacle in self.obstacles:
            obstacle.startMoving(elapsedTime)

        for gatherable in self.gatherables:
            gatherable.show()

        self._visible = True

    def offstage(self):
        if not self._visible:
            return
        self._root.stash()
        for obstacle in self.obstacles:
            obstacle.stopMoving()

        for gatherable in self.gatherables:
            gatherable.hide()

        self._visible = False

    def update(self, dt):
        if self._visible:
            for gatherable in self.gatherables:
                gatherable.update(dt)

            for obstacle in self.obstacles:
                obstacle.update(dt)

    def getModel(self):
        return self._root
Exemple #4
0
class CogdoMazeFactory:

    def __init__(self, randomNumGen, width, height, frameWallThickness=Globals.FrameWallThickness, cogdoMazeData=CogdoMazeData):
        self._rng = RandomNumGen(randomNumGen)
        self.width = width
        self.height = height
        self.frameWallThickness = frameWallThickness
        self._cogdoMazeData = cogdoMazeData
        self.quadrantSize = self._cogdoMazeData.QuadrantSize
        self.cellWidth = self._cogdoMazeData.QuadrantCellWidth

    def getMazeData(self):
        if not hasattr(self, '_data'):
            self._generateMazeData()
        return self._data

    def createCogdoMaze(self, flattenModel=True):
        if not hasattr(self, '_maze'):
            self._loadAndBuildMazeModel(flatten=flattenModel)
        return CogdoMaze(self._model, self._data, self.cellWidth)

    def _gatherQuadrantData(self):
        self.openBarriers = []
        barrierItems = range(Globals.TotalBarriers)
        self._rng.shuffle(barrierItems)
        for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]:
            self.openBarriers.append(i)

        self.quadrantData = []
        quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys()
        self._rng.shuffle(quadrantKeys)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                key = quadrantKeys[i]
                collTable = self._cogdoMazeData.QuadrantCollisions[key]
                angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)]
                self.quadrantData.append((key, collTable[angle], angle))
                i += 1
                if x * y >= self._cogdoMazeData.NumQuadrants:
                    i = 0

    def _generateBarrierData(self):
        data = []
        for y in xrange(self.height):
            data.append([])
            for x in xrange(self.width):
                if x == self.width - 1:
                    ax = -1
                else:
                    ax = 1
                if y == self.height - 1:
                    ay = -1
                else:
                    ay = 1
                data[y].append([ax, ay])

        dirUp = 0
        dirDown = 1
        dirLeft = 2
        dirRight = 3

        def getAvailableDirections(ax, ay, ignore=None):
            dirs = []
            if ax - 1 >= 0 and data[ay][(ax - 1)][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirLeft)
            if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirRight)
            if ay - 1 >= 0 and data[(ay - 1)][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirDown)
            if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirUp)
            return dirs

        visited = []

        def tryVisitNeighbor(ax, ay, ad):
            if ad == dirUp:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_TOP] = 0
                ay += 1
            elif ad == dirDown:
                if data[(ay - 1)][ax] in visited:
                    return None
                visited.append(data[(ay - 1)][ax])
                data[(ay - 1)][ax][BARRIER_DATA_TOP] = 0
                ay -= 1
            elif ad == dirLeft:
                if data[ay][(ax - 1)] in visited:
                    return None
                visited.append(data[ay][(ax - 1)])
                data[ay][(ax - 1)][BARRIER_DATA_RIGHT] = 0
                ax -= 1
            elif ad == dirRight:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_RIGHT] = 0
                ax += 1
            return (ax, ay)

        def openBarriers(x, y):
            dirs = getAvailableDirections(x, y)
            for dir in dirs:
                next = tryVisitNeighbor(x, y, dir)
                if next is not None:
                    openBarriers(*next)

            return

        x = self._rng.randint(0, self.width - 1)
        y = self._rng.randint(0, self.height - 1)
        openBarriers(x, y)
        self._barrierData = data
        return

    def _generateMazeData(self):
        if not hasattr(self, 'quadrantData'):
            self._gatherQuadrantData()
        self._data = {}
        self._data['width'] = (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize
        self._data['height'] = (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize
        self._data['originX'] = int(self._data['width'] / 2)
        self._data['originY'] = int(self._data['height'] / 2)
        collisionTable = []
        horizontalWall = [ 1 for x in xrange(self._data['width']) ]
        collisionTable.append(horizontalWall)
        for i in xrange(0, len(self.quadrantData), self.width):
            for y in xrange(self.quadrantSize):
                row = [
                 1]
                for x in xrange(i, i + self.width):
                    if x == 1 and y < self.quadrantSize / 2 - 2:
                        newData = []
                        for j in self.quadrantData[x][1][y]:
                            if j == 0:
                                newData.append(2)
                            else:
                                newData.append(j + 0)

                        row += newData + [1]
                    else:
                        row += self.quadrantData[x][1][y] + [1]

                collisionTable.append(row)

            collisionTable.append(horizontalWall[:])

        barriers = Globals.MazeBarriers
        for i in xrange(len(barriers)):
            for coords in barriers[i]:
                collisionTable[coords[1]][coords[0]] = 0

        y = self._data['originY']
        for x in xrange(len(collisionTable[y])):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        x = self._data['originX']
        for y in xrange(len(collisionTable)):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        self._data['collisionTable'] = collisionTable

    def _loadAndBuildMazeModel(self, flatten=False):
        self.getMazeData()
        self._model = NodePath('CogdoMazeModel')
        levelModel = CogdoUtil.loadMazeModel('level')
        self.quadrants = []
        quadrantUnitSize = int(self.quadrantSize * self.cellWidth)
        frameActualSize = self.frameWallThickness * self.cellWidth
        size = quadrantUnitSize + frameActualSize
        halfWidth = int(self.width / 2)
        halfHeight = int(self.height / 2)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                ax = (x - halfWidth) * size
                ay = (y - halfHeight) * size
                extension = ''
                if hasattr(getBase(), 'air'):
                    extension = '.bam'
                filepath = self.quadrantData[i][0] + extension
                angle = self.quadrantData[i][2]
                m = self._createQuadrant(filepath, i, angle, quadrantUnitSize)
                m.setPos(ax, ay, 0)
                m.reparentTo(self._model)
                self.quadrants.append(m)
                i += 1

        quadrantHalfUnitSize = quadrantUnitSize * 0.5
        barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider')
        y = 3
        for x in xrange(self.width):
            if x == (self.width - 1) / 2:
                continue
            ax = (x - halfWidth) * size
            ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5)
            b = NodePath('barrier')
            barrierModel.instanceTo(b)
            b.setPos(ax, ay, 0)
            b.reparentTo(self._model)

        offset = self.cellWidth - 0.5
        for x in (0, 3):
            for y in xrange(self.height):
                ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset
                ay = (y - halfHeight) * size
                b = NodePath('barrier')
                barrierModel.instanceTo(b)
                b.setPos(ax, ay, 0)
                b.setH(90)
                b.reparentTo(self._model)

            offset -= 2.0

        barrierModel.removeNode()
        levelModel.getChildren().reparentTo(self._model)
        for np in self._model.findAllMatches('**/*lightCone*'):
            CogdoUtil.initializeLightCone(np, 'fixed', 3)

        if flatten:
            self._model.flattenStrong()
        return self._model

    def _createQuadrant(self, filepath, serialNum, angle, size):
        root = NodePath('QuadrantRoot-%i' % serialNum)
        quadrant = loader.loadModel(filepath)
        quadrant.getChildren().reparentTo(root)
        root.setH(angle)
        return root
    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()
Exemple #6
0
class GunGameLevelLoader:
    notify = directNotify.newCategory('GunGameLevelLoader')
    LevelData = {'momada': {'name': CIGlobals.ToonBattleOriginalLevel, 
                  'camera': (
                           Point3(0.0, -25.8, 7.59), Vec3(0.0, 0.0, 0.0)), 
                  'models': [
                           'phase_11/models/lawbotHQ/LB_Zone03a.bam',
                           'phase_11/models/lawbotHQ/LB_Zone04a.bam',
                           'phase_11/models/lawbotHQ/LB_Zone7av2.bam',
                           'phase_11/models/lawbotHQ/LB_Zone08a.bam',
                           'phase_11/models/lawbotHQ/LB_Zone13a.bam',
                           'phase_10/models/cashbotHQ/ZONE17a.bam',
                           'phase_10/models/cashbotHQ/ZONE18a.bam',
                           'phase_11/models/lawbotHQ/LB_Zone22a.bam'], 
                  'parents': [
                            render,
                            'EXIT',
                            'EXIT',
                            'EXIT',
                            'ENTRANCE',
                            'ENTRANCE',
                            'ENTRANCE',
                            'EXIT'], 
                  'model_positions': [
                                    Point3(0.0, 0.0, 0.0),
                                    Point3(-1.02, 59.73, 0.0),
                                    Point3(0.0, 74.77, 0.0),
                                    Point3(0.0, 89.37, -13.5),
                                    Point3(16.33, -136.53, 0.0),
                                    Point3(-1.01, -104.4, 0.0),
                                    Point3(0.65, -23.86, 0.0),
                                    Point3(-55.66, -29.01, 0.0)], 
                  'model_orientations': [
                                       Vec3(0.0, 0.0, 0.0),
                                       Vec3(0.0, 0.0, 0.0),
                                       Vec3(90.0, 0.0, 0.0),
                                       Vec3(180.0, 0.0, 0.0),
                                       Vec3(97.0, 0.0, 0.0),
                                       Vec3(359.95, 0.0, 0.0),
                                       Vec3(90.0, 0.0, 0.0),
                                       Vec3(270.0, 0.0, 0.0)], 
                  'spawn_points': [
                                 (
                                  Point3(0, 0, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(-20, 50, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(20, 50, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(0, 120, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(0, 100, 0), Vec3(180, 0, 0)),
                                 (
                                  Point3(-90, 0, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(-170, 0, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(-90, 50, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(-170, 50, 0), Vec3(0, 0, 0)),
                                 (
                                  Point3(35, 250, 0), Vec3(-90, 0, 0)),
                                 (
                                  Point3(0, 285, 0), Vec3(180, 0, 0)),
                                 (
                                  Point3(-185, 250, 0), Vec3(90, 0, 0))]}, 
       'dg': {'name': CIGlobals.DaisyGardens, 
              'camera': (
                       Point3(-33.13, -3.2, 48.62), Vec3(326.31, 332.68, 0.0)), 
              'dna': [
                    'phase_8/dna/storage_DG.pdna',
                    'phase_8/dna/storage_DG_sz.pdna',
                    'phase_8/dna/daisys_garden_sz.pdna'], 
              'sky': 'TT', 
              'spawn_points': hoodMgr.dropPoints[CIGlobals.DaisyGardens]}, 
       'mml': {'name': CIGlobals.MinniesMelodyland, 
               'camera': (
                        Point3(-54.42, -91.05, 34.89), Vec3(315.29, 336.8, 0.0)), 
               'dna': [
                     'phase_6/dna/storage_MM.pdna',
                     'phase_6/dna/storage_MM_sz.pdna',
                     'phase_6/dna/minnies_melody_land_sz.pdna'], 
               'sky': 'MM', 
               'spawn_points': hoodMgr.dropPoints[CIGlobals.MinniesMelodyland]}, 
       'oz': {'name': CIGlobals.OutdoorZone, 
              'camera': (
                       Point3(-54.42, -91.05, 34.89), Vec3(315.29, 336.8, 0.0)), 
              'dna': [
                    'phase_6/dna/storage_OZ.pdna',
                    'phase_6/dna/storage_OZ_sz.pdna',
                    'phase_6/dna/outdoor_zone_sz.pdna'], 
              'sky': 'TT', 
              'spawn_points': hoodMgr.dropPoints[CIGlobals.OutdoorZone]}, 
       'cbhq': {'name': CIGlobals.CashbotHQ, 
                'camera': (
                         Point3(302.64, 5.0, 15.2), Vec3(135.0, 341.57, 0.0)), 
                'model': 'phase_10/models/cogHQ/CashBotShippingStation.bam', 
                'sky': None, 
                'spawn_points': hoodMgr.dropPoints[CIGlobals.CashbotHQ]}, 
       'sbf': {'name': CIGlobals.SellbotFactory, 
               'camera': (
                        Point3(0, 0, 0), Vec3(0, 0, 0)), 
               'model': 'phase_9/models/cogHQ/SelbotLegFactory.bam', 
               'sky': 'cog', 
               'sky_scale': 10.0, 
               'occluders': 'phase_9/models/cogHQ/factory_sneak_occluders.egg', 
               'spawn_points': {GGG.Teams.BLUE: [
                                               (
                                                Point3(13, 30, 3.73), Point3(0, 0, 0)), (Point3(21, 30, 3.73), Point3(0, 0, 0)), (Point3(29, 30, 3.73), Point3(0, 0, 0)),
                                               (
                                                Point3(13, 20, 3.73), Point3(0, 0, 0)), (Point3(21, 20, 3.73), Point3(0, 0, 0)), (Point3(29, 30, 3.73), Point3(0, 0, 0))], 
                                GGG.Teams.RED: [
                                              (
                                               Point3(-644.43, 378.12, 8.73), Point3(270, 0, 0)), (Point3(-644.43, 370.75, 8.73), Point3(270, 0, 0)), (Point3(-644.43, 363.22, 8.73), Point3(270, 0, 0)),
                                              (
                                               Point3(-659.05, 378.12, 8.73), Point3(270, 0, 0)), (Point3(-659.05, 370.75, 8.73), Point3(270, 0, 0)), (Point3(-659.05, 363.22, 8.73), Point3(270, 0, 0))]}, 
               'flag_points': {GGG.Teams.BLUE: [Point3(213.23, 340.59, 19.73), Point3(90, 0, 0)], GGG.Teams.RED: [
                                             Point3(-543.6, 595.79, 9.73), Point3(270, 0, 0)]}, 
               'flagpoint_points': {GGG.Teams.BLUE: [Point3(-543.6, 595.79, 9.73), Point3(270, 0, 0)], GGG.Teams.RED: [
                                                  Point3(213.23, 340.59, 19.73), Point3(0, 0, 0)]}}, 
       'ttc': {'name': CIGlobals.ToontownCentral, 
               'dna': [
                     'phase_4/dna/storage_TT.pdna',
                     'phase_4/dna/storage_TT_sz.pdna',
                     'phase_4/dna/new_ttc_sz.pdna'], 
               'sky': 'TT', 
               'spawn_points': [
                              (9.90324401855, 91.9139556885, 8.0364112854, -545.909545898, 0.0, 0.0),
                              (77.9181442261, 50.953086853, 7.52815723419, -598.509460449, 0.0, 0.0),
                              (93.7379760742, 6.37303066254, 7.99749088287, -626.209533691, 0.0, 0.0),
                              (39.0383415222, -81.5989837646, 8.01874637604, -694.309265137, 0.0, 0.0),
                              (-19.2093048096, -95.1359481812, 8.07303524017, -731.409240723, 0.0, 0.0),
                              (-84.4093933105, -45.4780502319, 8.06541728973, -781.809143066, 0.0, 0.0),
                              (-92.2512283325, 2.41426730156, 8.03108692169, -811.70916748, 0.0, 0.0),
                              (46.8868179321, 81.3593673706, 8.04793071747, -955.309509277, 0.0, 0.0),
                              (32.3203735352, 90.0017929077, 8.06353855133, -884.409301758, 0.0, 0.0)], 
               'cap_point': Point3(-1.5, 0, 0)}}
    SkyData = {'TT': 'phase_3.5/models/props', 
       'MM': 'phase_6/models/props', 
       'cog': 'phase_9/models/cogHQ', 
       'MovingSkies': [
                     'TT']}

    def __init__(self, mg):
        self.mg = mg
        self.levelName = None
        self.dnaStore = DNAStorage()
        self.loadingText = None
        self.levelGeom = None
        self.skyUtil = None
        self.skyModel = None
        self.occluders = None
        self.momadaAreas = []
        self.momadaAreaName2areaModel = {}
        return

    def getFlagPoint_Point(self, team):
        return self.LevelData[self.levelName]['flagpoint_points'][team]

    def getFlagPoint(self, team):
        return self.LevelData[self.levelName]['flag_points'][team]

    def getCapturePoint(self):
        return self.LevelData[self.levelName]['cap_point']

    def setLevel(self, level):
        self.levelName = level

    def getLevel(self):
        return self.levelName

    def getCameraOfCurrentLevel(self):
        return self.LevelData[self.getLevel()]['camera']

    def getSpawnPoints(self):
        pointData = self.LevelData[self.levelName]['spawn_points']
        if self.levelName == 'momada':
            return pointData
        if self.mg.gameMode in [GGG.GameModes.CASUAL, GGG.GameModes.KOTH]:
            array = []
            for posAndHpr in pointData:
                array.append((
                 Point3(posAndHpr[0], posAndHpr[1], posAndHpr[2]),
                 Vec3(posAndHpr[3], posAndHpr[4], posAndHpr[5])))

        else:
            if self.mg.gameMode == GGG.GameModes.CTF:
                array = pointData[self.mg.team]
        return array

    def getNameOfCurrentLevel(self):
        return self.LevelData[self.getLevel()]['name']

    def load(self):
        self.unload()
        if self.loadingText:
            self.loadingText.destroy()
            self.loadingText = None
        self.loadingText = OnscreenText(text='', font=CIGlobals.getMinnieFont(), fg=(1,
                                                                                     1,
                                                                                     1,
                                                                                     1))
        self.loadingText.setBin('gui-popup', 0)
        base.graphicsEngine.renderFrame()
        base.graphicsEngine.renderFrame()
        if self.levelName == 'momada':
            self.__momadaLoad()
        else:
            if self.levelName in ('cbhq', 'sbf'):
                modelPath = self.LevelData[self.levelName]['model']
                self.levelGeom = loader.loadModel(modelPath)
                self.levelGeom.flattenMedium()
                self.levelGeom.reparentTo(render)
                if self.LevelData[self.levelName]['sky'] != None:
                    self.skyModel = loader.loadModel(self.SkyData['cog'] + '/cog_sky.bam')
                    self.skyUtil = SkyUtil()
                    self.skyUtil.startSky(self.skyModel)
                    self.skyModel.reparentTo(render)
                    self.skyModel.setScale(self.LevelData[self.levelName].get('sky_scale', 1.0))
                if self.LevelData[self.levelName].get('occluders'):
                    self.occluders = loader.loadModel(self.LevelData[self.levelName]['occluders'])
                    for occluderNode in self.occluders.findAllMatches('**/+OccluderNode'):
                        base.render.setOccluder(occluderNode)
                        occluderNode.node().setDoubleSided(True)

                if self.levelName == 'sbf':
                    base.camLens.setFar(250)
            else:
                dnaFiles = self.LevelData[self.levelName]['dna']
                skyType = self.LevelData[self.levelName]['sky']
                skyPhase = self.SkyData[skyType]
                loadDNAFile(self.dnaStore, 'phase_4/dna/storage.pdna')
                for index in range(len(dnaFiles)):
                    if index == len(dnaFiles) - 1:
                        node = loadDNAFile(self.dnaStore, dnaFiles[index])
                        if node.getNumParents() == 1:
                            self.levelGeom = NodePath(node.getParent(0))
                            self.levelGeom.reparentTo(hidden)
                        else:
                            self.levelGeom = hidden.attachNewNode(node)
                        if self.levelName == 'ttc' and dnaFiles[index] == 'phase_4/dna/new_ttc_sz.pdna':
                            self.levelGeom.find('**/prop_gazebo_DNARoot').removeNode()
                        else:
                            self.levelGeom.flattenMedium()
                        gsg = base.win.getGsg()
                        if gsg:
                            self.levelGeom.prepareScene(gsg)
                        self.levelGeom.reparentTo(render)
                    else:
                        loadDNAFile(self.dnaStore, dnaFiles[index])

                children = self.levelGeom.findAllMatches('**/*doorFrameHole*')
                for child in children:
                    child.hide()

                self.skyModel = loader.loadModel(skyPhase + '/' + skyType + '_sky.bam')
                self.skyUtil = SkyUtil()
                self.skyUtil.startSky(self.skyModel)
                self.skyModel.reparentTo(camera)
                ce = CompassEffect.make(NodePath(), CompassEffect.PRot | CompassEffect.PZ)
                self.skyModel.node().setEffect(ce)
        if self.loadingText:
            self.loadingText.destroy()
            self.loadingText = None
        return

    def __momadaLoad(self):

        def attachArea(itemNum):
            name = 'MomadaArea-%s' % itemNum
            area = self.momadaAreaName2areaModel.get(name)
            parents = self.LevelData['momada']['parents']
            parent = parents[itemNum]
            if type(parent) == type(''):
                parent = self.momadaAreas[itemNum - 1].find('**/' + parent)
            pos = self.LevelData['momada']['model_positions'][itemNum]
            hpr = self.LevelData['momada']['model_orientations'][itemNum]
            area.reparentTo(parent)
            area.setPos(pos)
            area.setHpr(hpr)

        _numItems = 0
        name = None
        for item in self.LevelData['momada']['models']:
            name = 'MomadaArea-%s' % _numItems
            area = loader.loadModel(item)
            self.momadaAreas.append(area)
            self.momadaAreaName2areaModel[name] = area
            attachArea(_numItems)
            _numItems += 1
            self.notify.info('Loaded and attached %s momada areas.' % _numItems)

        return

    def unload(self):
        render.clearOccluder()
        if self.levelName == 'sbf':
            base.camLens.setFar(CIGlobals.DefaultCameraFar)
        if self.levelName == 'momada':
            for area in self.momadaAreas:
                self.momadaAreas.remove(area)
                area.removeNode()
                del area

            self.momadaAreas = []
            self.momadaAreaName2areaModel = {}
        else:
            if self.occluders:
                self.occluders.removeNode()
                self.occluders = None
            if self.skyUtil:
                self.skyUtil.stopSky()
                self.skyUtil = None
            if self.skyModel:
                self.skyModel.removeNode()
                self.skyModel = None
            if self.levelGeom:
                self.levelGeom.removeNode()
                self.levelGeom = None
        return

    def cleanup(self):
        self.momadaAreas = None
        self.momadaAreaName2areaModel = None
        if self.dnaStore:
            self.dnaStore.reset_nodes()
            self.dnaStore.reset_hood_nodes()
            self.dnaStore.reset_place_nodes()
            self.dnaStore.reset_hood()
            self.dnaStore.reset_fonts()
            self.dnaStore.reset_DNA_vis_groups()
            self.dnaStore.reset_textures()
            self.dnaStore.reset_block_numbers()
            self.dnaStore.reset_block_zones()
            self.dnaStore.reset_suit_points()
        self.dnaStore = None
        return
class CogdoFlyingLevelQuadrant:
    notify = directNotify.newCategory('CogdoFlyingLevelQuadrant')

    def __init__(self, serialNum, model, level, parent):
        self.serialNum = serialNum
        self._model = model
        self._level = level
        self._root = NodePath('Quadrant' + `serialNum`)
        self._model.reparentTo(self._root)
        self._root.reparentTo(parent)
        self._visible = True
        self.platforms = {}
        self.gatherables = []
        self.obstacles = []
        self._playing = False
        self._obstaclesRoot = NodePath('obstacles')
        self._obstaclesRoot.reparentTo(self._root)
        self._initObstacles(self._obstaclesRoot)
        self._gatherablesRoot = NodePath('gatherables')
        self._gatherablesRoot.reparentTo(self._root)
        self._initGatherables(self._gatherablesRoot)
        self._platformsRoot = NodePath('platforms')
        self._platformsRoot.reparentTo(self._model)
        self._initPlatforms(self._platformsRoot)
        self._optimize()
        self.place()

    def _optimize(self):
        lightCones = NodePath('lightCones')
        for np in self._platformsRoot.findAllMatches('**/*lightCone*'):
            np.wrtReparentTo(lightCones)

        lightCones.reparentTo(self._model)
        node = self._model.find('**/ducts')
        if not node.isEmpty():
            node.flattenStrong()
            for np in node.getChildren():
                np.wrtReparentTo(self._model)

        node = self._model.find('**/nests')
        if not node.isEmpty():
            for np in node.getChildren():
                np.flattenStrong()
                np.wrtReparentTo(self._model)

        for np in self._model.findAllMatches('**/*LayerStack*'):
            np.wrtReparentTo(self._model)

        for np in self._model.find('**/static').getChildren():
            np.wrtReparentTo(self._model)

        self._model.flattenMedium()

    def _initPlatforms(self, parent):
        platformModels = self._model.findAllMatches('**/%s' % Globals.Level.PlatformName)
        for platformModel in platformModels:
            platform = CogdoFlyingPlatform(platformModel, parent=parent)
            self.platforms[platform.getName()] = platform

    def _destroyPlatforms(self):
        for platform in self.platforms.values():
            platform.destroy()

        del self.platforms

    def _initGatherables(self, parent):
        self.generateGatherables(self._model, parent=parent)
        if Globals.Level.SpawnLaffPowerupsInNests:
            self.generateNestPowerups(self._model, parent=parent)

    def generateNestPowerups(self, gatherableModel, parent):
        nests = gatherableModel.findAllMatches('**/%s;+s' % Globals.Level.LegalEagleNestName)
        for nest in nests:
            offset = Globals.Level.LaffPowerupNestOffset
            pickup = self._level.gatherableFactory.createPowerup(Globals.Level.GatherableTypes.LaffPowerup)
            pickup.reparentTo(parent)
            pickup.setPos(parent, nest.getPos(parent) + offset)
            if Globals.Level.AddSparkleToPowerups:
                sparkles = self._level.gatherableFactory.createSparkles(Vec4(1, 1, 1, 1), Vec4(1, 1, 0, 1), 10.0)
                sparkles.reparentTo(pickup)
                sparkles.setPos(0, 0, 1)
                sparkles.start()
            self.gatherables.append(pickup)

    def generateGatherables(self, gatherableModel, parent = None, spread = Globals.Level.GatherablesDefaultSpread):
        parent = parent or self._root
        mopath = Mopath.Mopath(name='gatherables')

        def generateMemos():
            gatherPaths = gatherableModel.findAllMatches('**/%s' % Globals.Level.GatherablesPathName)
            for gatherPath in gatherPaths:
                mopath.loadNodePath(gatherPath)
                t = 0.0
                while t < mopath.getMaxT():
                    pickup = self._level.gatherableFactory.createMemo()
                    pickup.reparentTo(parent)
                    mopath.goTo(pickup, t)
                    self.gatherables.append(pickup)
                    t += spread

                gatherPath.removeNode()

            angleSpread = 360.0 / Globals.Level.NumMemosInRing
            gatherPaths = gatherableModel.findAllMatches('**/%s' % Globals.Level.GatherablesRingName)
            for gatherPath in gatherPaths:
                mopath.loadNodePath(gatherPath)
                t = 0.0
                while t < mopath.getMaxT():
                    angle = 0
                    r = 3
                    while angle < 360.0:
                        pickup = self._level.gatherableFactory.createMemo()
                        pickup.reparentTo(parent)
                        mopath.goTo(pickup, t)
                        pickup.setX(parent, pickup.getX() + r * math.cos(math.radians(angle)))
                        pickup.setZ(parent, pickup.getZ() + r * math.sin(math.radians(angle)))
                        self.gatherables.append(pickup)
                        angle += angleSpread

                    t += spread + 0.5

                gatherPath.removeNode()

        def generatePropellers():
            gatherables = gatherableModel.findAllMatches('**/%s' % Globals.Level.PropellerName)
            for gatherable in gatherables:
                pickup = self._level.gatherableFactory.createPropeller()
                pickup.reparentTo(gatherable.getParent())
                pickup.setPos(parent, gatherable.getPos(parent))
                self.gatherables.append(pickup)
                gatherable.removeNode()

        def generatePowerUps():
            for powerupType, locName in Globals.Level.PowerupType2Loc.iteritems():
                if powerupType == Globals.Level.GatherableTypes.LaffPowerup and Globals.Level.IgnoreLaffPowerups:
                    continue
                gatherables = gatherableModel.findAllMatches('**/%s' % locName)
                for gatherable in gatherables:
                    pickup = self._level.gatherableFactory.createPowerup(powerupType)
                    pickup.reparentTo(parent)
                    pickup.setPos(parent, gatherable.getPos(parent))
                    if Globals.Level.AddSparkleToPowerups:
                        sparkles = self._level.gatherableFactory.createSparkles(Vec4(1, 1, 1, 1), Vec4(1, 1, 0, 1), 10.0)
                        sparkles.reparentTo(pickup)
                        sparkles.setPos(0, 0, 1)
                        sparkles.start()
                    self.gatherables.append(pickup)
                    gatherable.removeNode()

        generateMemos()
        generatePropellers()
        generatePowerUps()

    def _initObstacles(self, parent):

        def initWhirlwinds():
            obstacles = self._root.findAllMatches('**/%s' % Globals.Level.WhirlwindName)
            for obstacleLoc in obstacles:
                motionPath = self._model.find('**/%s%s' % (obstacleLoc.getName(), Globals.Level.WhirlwindPathName))
                if motionPath.isEmpty():
                    motionPath = None
                obstacle = self._level.obstacleFactory.createWhirlwind(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, obstacleLoc.getPos(parent))
                self.obstacles.append(obstacle)
                obstacleLoc.removeNode()

            return

        def initStreamers():
            obstacles = self._model.findAllMatches('**/%s' % Globals.Level.StreamerName)
            for obstacleLoc in obstacles:
                obstacle = self._level.obstacleFactory.createFan()
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, obstacleLoc.getPos(parent))
                obstacle.model.setHpr(parent, obstacleLoc.getHpr(parent))
                obstacle.model.setScale(parent, obstacleLoc.getScale(parent))
                obstacle.setBlowDirection()
                if Globals.Level.AddParticlesToStreamers:
                    particles = self._level.obstacleFactory.createStreamerParticles(Vec4(1, 1, 1, 1), Vec4(1, 1, 1, 1), 10.0)
                    particles.reparentTo(obstacle.model)
                    particles.start()
                self.obstacles.append(obstacle)
                obstacleLoc.removeNode()

        def initWalkingMinions():
            motionPaths = self._model.findAllMatches('**/%s' % Globals.Level.MinionWalkingPathName)
            for motionPath in motionPaths:
                obstacle = self._level.obstacleFactory.createWalkingMinion(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, motionPath.getPos(parent))
                self.obstacles.append(obstacle)

        def initFlyingMinions():
            motionPaths = self._model.findAllMatches('**/%s' % Globals.Level.MinionFlyingPathName)
            for motionPath in motionPaths:
                obstacle = self._level.obstacleFactory.createFlyingMinion(motionPath=motionPath)
                obstacle.model.reparentTo(parent)
                obstacle.model.setPos(parent, motionPath.getPos(parent))
                self.obstacles.append(obstacle)

        initWhirlwinds()
        initStreamers()
        initWalkingMinions()
        initFlyingMinions()

    def place(self):
        self._root.setPos(0, self._level.convertQuadNumToY(self.serialNum), 0)

    def destroy(self):
        if self._visible:
            self.offstage()
        self._destroyPlatforms()
        for obstacle in self.obstacles:
            obstacle.destroy()

        for gatherable in self.gatherables:
            gatherable.destroy()

        self._root.removeNode()
        del self._root
        del self._gatherablesRoot
        del self._obstaclesRoot
        del self._platformsRoot
        del self._level

    def onstage(self, elapsedTime = 0.0):
        if self._visible:
            return
        self._root.unstash()
        for obstacle in self.obstacles:
            obstacle.startMoving(elapsedTime)

        for gatherable in self.gatherables:
            gatherable.show()

        self._visible = True

    def offstage(self):
        if not self._visible:
            return
        self._root.stash()
        for obstacle in self.obstacles:
            obstacle.stopMoving()

        for gatherable in self.gatherables:
            gatherable.hide()

        self._visible = False

    def update(self, dt):
        if self._visible:
            for gatherable in self.gatherables:
                gatherable.update(dt)

            for obstacle in self.obstacles:
                obstacle.update(dt)

    def getModel(self):
        return self._root
Exemple #8
0
class cMinicolumn:
    def __init__(self, nameOfLayer, nOfCellsPerColumn):
        self.cells = []
        for i in range(nOfCellsPerColumn):
            n = cCell(self)
            self.cells.append(n)

        self.parentLayer = nameOfLayer
        self.transparency = 1.0
        self.gfxCreated = False
        self.LodDistance1Stored = 100.0
        self.LodDistance2Stored = 5000.0

        self.bursting = False
        self.active = False
        self.oneOfCellPredictive = False
        self.oneOfCellCorrectlyPredicted = False
        self.oneOfCellFalselyPredicted = False

    def CreateGfx(self, loader, idx):
        #                __node
        #                /   \
        #  cellsNodePath   columnBox

        self.lod = LODNode("columnLOD")  # Level of detail node for Column
        self.__node = NodePath(
            self.lod
        )  # NodePath(PandaNode('column'))# loader.loadModel("models/box")
        self.__node.setPos(0, 0, 0)
        self.__node.setScale(1, 1, 1)

        # self.__node.setTag('clickable',str(idx))#to be able to click on it

        self.__columnBox = loader.loadModel("models/cube")
        self.__columnBox.setPos(
            0, 0, -0.5 + (0 if len(self.cells) == 0 else len(self.cells) *
                          (1 + CELL_OFFSET) / 2))
        self.__columnBox.setScale(
            0.5, 0.5, 0.5 * (1 if len(self.cells) == 0 else len(self.cells) *
                             (1 + CELL_OFFSET)))
        self.__columnBox.setName("columnBox")

        self.__cellsNodePath = NodePath(
            PandaNode("cellsNode"))  # to pack all cells into one node path
        self.__cellsNodePath.setName("column")
        self.__cellsNodePath.setTag(
            "id",
            str(idx))  # to be able to retrieve index of column for mouse click

        self.lod.addSwitch(100.0, 0.0)
        self.lod.addSwitch(5000.0, 100.0)

        self.__cellsNodePath.reparentTo(self.__node)
        self.__columnBox.reparentTo(self.__node)

        z = 0
        idx = 0
        for n in self.cells:
            n.CreateGfx(loader, idx)
            idx += 1
            n.getNode().setPos(0, 0, z)
            z += 1 + CELL_OFFSET
            n.getNode().reparentTo(self.__cellsNodePath)

        self.gfxCreated = True

    def LODUpdateSwitch(self, lodDistance, lodDistance2):
        self.lodDistance1Stored = lodDistance
        self.lodDistance2Stored = lodDistance2

        self.lod.clearSwitches()
        self.lod.addSwitch(lodDistance, 0.0)
        self.lod.addSwitch(lodDistance2, lodDistance)

    def LODUpdateSwitch_long(self):
        self.lod.clearSwitches()
        self.lod.addSwitch(self.lodDistance2Stored, 0.0)
        self.lod.addSwitch(self.lodDistance2Stored, self.lodDistance2Stored)

    def LODUpdateSwitch_normal(self):
        self.LODUpdateSwitch(self.lodDistance1Stored, self.lodDistance2Stored)

    def UpdateState(self, bursting, activeColumn, oneOfCellPredictive,
                    oneOfCellCorrectlyPredicted, oneOfCellFalselyPredicted):

        self.bursting = bursting
        self.active = activeColumn
        self.oneOfCellPredictive = oneOfCellPredictive
        self.oneOfCellCorrectlyPredicted = oneOfCellCorrectlyPredicted
        self.oneOfCellFalselyPredicted = oneOfCellFalselyPredicted

        # update column box color (for LOD in distance look)
        if self.oneOfCellCorrectlyPredicted:
            COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED
            self.__columnBox.setColor(col)
        elif self.oneOfCellFalselyPredicted:
            COL_COLUMN_ONEOFCELLFALSELY_PREDICTED.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLFALSELY_PREDICTED
            self.__columnBox.setColor(col)
        elif self.oneOfCellPredictive:
            COL_COLUMN_ONEOFCELLPREDICTIVE.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLPREDICTIVE
            self.__columnBox.setColor(col)
        elif self.active:
            COL_COLUMN_ACTIVE.setW(self.transparency)
            col = COL_COLUMN_ACTIVE
            self.__columnBox.setColor(col)
        else:
            COL_COLUMN_INACTIVE.setW(self.transparency)
            col = COL_COLUMN_INACTIVE
            self.__columnBox.setColor(col)

#        for n in self.cells:
#            n.active = active
#            n.UpdateState()

    def getNode(self):
        return self.__node

    def updateWireframe(self, value):
        for cell in self.cells:
            cell.updateWireframe(value)
        if value:
            self.__columnBox.setRenderModeFilledWireframe(LColor(0, 0, 0, 1.0))
        else:
            self.__columnBox.setRenderModeFilled()

    # -- Create proximal synapses
    # inputObjects - list of names of inputs(areas)
    # inputs - panda vis input object
    # synapses - list of the second points of synapses (first point is this cortical column)
    # NOTE: synapses are now DENSE
    def CreateProximalSynapses(self, inputObjects, inputs, synapses):

        for child in self.__cellsNodePath.getChildren():
            if child.getName() == "ProximalSynapseLine":
                child.removeNode()

        printLog("Creating proximal synapses", verbosityMedium)
        printLog("To inputs called:" + str(inputObjects), verbosityMedium)
        printLog("Synapses count:" + str(len(synapses)), verbosityMedium)
        printLog("active:" + str(sum([i for i in synapses])), verbosityHigh)

        # inputs are divided into separate items in list - [input1,input2,input3]
        # synapses are one united array [1,0,0,1,0,1,0...]
        # length is the same

        # synapses can be connected to one input or to several inputs
        # if to more than one - split synapses array
        synapsesDiv = []
        offset = 0
        for inputObj in inputObjects:
            synapsesDiv.append(synapses[offset:offset +
                                        inputs[inputObj].count])
            offset += inputs[inputObj].count

        for i in range(len(synapsesDiv)):  # for each input object

            inputs[
                inputObjects[i]].resetProximalFocus()  # clear color highlight

            for y in range(len(synapsesDiv[i])
                           ):  # go through every synapse and check activity
                if synapsesDiv[i][y] == 1:

                    form = GeomVertexFormat.getV3()
                    vdata = GeomVertexData("ProximalSynapseLine", form,
                                           Geom.UHStatic)
                    vdata.setNumRows(1)
                    vertex = GeomVertexWriter(vdata, "vertex")

                    vertex.addData3f(
                        inputs[inputObjects[i]].inputBits[y].getNode().getPos(
                            self.__node))
                    vertex.addData3f(0, 0, 0)
                    # vertex.addData3f(self.__node.getPos())
                    # printLog("Inputs:"+str(i)+"bits:"+str(y))
                    # printLog(inputs[i].inputBits[y].getNode().getPos(self.__node))

                    # highlight
                    inputs[inputObjects[i]].inputBits[y].setProximalFocus(
                    )  # highlight connected bits

                    prim = GeomLines(Geom.UHStatic)
                    prim.addVertices(0, 1)

                    geom = Geom(vdata)
                    geom.addPrimitive(prim)

                    node = GeomNode("ProximalSynapse")
                    node.addGeom(geom)

                    nodePath = self.__cellsNodePath.attachNewNode(node)

                    nodePath.setRenderModeThickness(2)
                    # color of the line
                    if inputs[inputObjects[i]].inputBits[y].active:
                        nodePath.setColor(COL_PROXIMAL_SYNAPSES_ACTIVE)
                    else:
                        nodePath.setColor(COL_PROXIMAL_SYNAPSES_INACTIVE)

    def setTransparency(self, transparency):
        self.transparency = transparency
        for cell in self.cells:
            cell.setTransparency(transparency)

        self.UpdateState(self.bursting, self.active, self.oneOfCellPredictive,
                         self.oneOfCellCorrectlyPredicted,
                         self.oneOfCellFalselyPredicted)

    def DestroyProximalSynapses(self):
        for syn in self.__cellsNodePath.findAllMatches("ProximalSynapse"):
            syn.removeNode()

    def DestroyDistalSynapses(self):
        for cell in self.cells:
            cell.DestroyDistalSynapses()
            cell.resetPresynapticFocus()  # also reset distal focus

    def getDescription(self):
        txt = ""
        txt += "Active:" + str(self.active) + "\n"
        txt += "One of cell is predictive:" + str(
            self.oneOfCellPredictive) + "\n"
        txt += "One of cell correctly predicted:" + str(
            self.oneOfCellCorrectlyPredicted) + "\n"
        txt += "One of cell false predicted:" + str(
            self.oneOfCellFalselyPredicted) + "\n"
        return txt
class CharSelection(DirectObject):
    notify = directNotify.newCategory('CharSelection')

    NO_TOON = "Empty Slot"
    PLAY = "Play"
    CREATE = "Create"
    TITLE = "Pick  A  Toon  To  Play"

    def __init__(self, avChooser):
        self.avChooser = avChooser
        self.choice = None
        self.charList = None
        self.charNameLabel = None
        self.charButtons = []
        self.playOrCreateButton = None
        self.deleteButton = None
        self.quitButton = None
        self.title = None
        self.stageToon = None
        self.stageToonRoot = None
        self.deleteConf = None
        self.frame = None
        self.stageFSM = ClassicFSM.ClassicFSM('StageFSM', [
            State.State('off', self.enterOff, self.exitOff),
            State.State('loadSZ', self.enterLoadSZ, self.exitLoadSZ),
            State.State('onStage', self.enterOnStage, self.exitOnStage)
        ], 'off', 'off')
        self.stageFSM.enterInitialState()
        self.selectionFSM = ClassicFSM.ClassicFSM('CharSelection', [
            State.State('off', self.enterOff, self.exitOff),
            State.State('character', self.enterCharSelected,
                        self.exitCharSelected),
            State.State('empty', self.enterEmptySelected,
                        self.exitEmptySelected)
        ], 'off', 'off')
        self.selectionFSM.enterInitialState()

        self.szGeom = None
        self.olc = None
        self.asyncSZLoadStatus = False
        self.isNewToon = False
        self.newToonSlot = None
        self.camIval = None
        self.stAnimSeq = None
        self.newToonAnnounceSfx = base.loadSfx(
            "phase_4/audio/sfx/King_Crab.ogg")
        self.newToonDrumrollSfx = base.loadSfx(
            "phase_5/audio/sfx/SZ_MM_drumroll.ogg")
        self.newToonRevealSfx = base.loadSfx(
            "phase_5/audio/sfx/SZ_MM_fanfare.ogg")

        self.dnaStore = DNAStorage()
        loader.loadDNAFile(self.dnaStore,
                           'phase_4/dna/pickatoon/storage_pickatoon.pdna')

    def __setupStageToon(self):
        self.stageToonRoot = render.attachNewNode('stageToonRoot')
        self.stageToon = Toon(base.cr)
        self.stageToon.setPosHpr(0, 0, 0, 0, 0, 0)
        self.stageToon.reparentTo(self.stageToonRoot)

    def cleanupStageToon(self):
        if self.stageToon != None:
            self.stageToon.disable()
            self.stageToon.delete()
            self.stageToon = None
        if self.stageToonRoot != None:
            self.stageToonRoot.removeNode()
            self.stageToonRoot = None

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def __async_loadSZTask(self, task=None):
        dnas = HOOD_ID_2_DNA[self.choice.lastHood]
        for i in xrange(len(dnas)):
            dnaFile = dnas[i]
            if i == len(dnas) - 1:
                node = loader.loadDNAFile(self.dnaStore, dnaFile)
                if node.getNumParents() == 1:
                    self.szGeom = NodePath(node.getParent(0))
                    self.szGeom.reparentTo(render)
                else:
                    self.szGeom = render.attachNewNode(node)

                # The 2D trees should not be flattened, to do that, we're going to traverse
                # the scene graph for all trees, use #wrtReparentTo(render) on them, flatten
                # the scene with #flattenStrong(), and finally #wrtReparentTo(self.szGeom)
                # the trees back to the main scene node so they get cleaned up properly.
                trees = self.szGeom.findAllMatches('**/*tree*')
                #self.szGeom.find("**/shadow").removeNode()

                #from panda3d.core import CullBinAttrib
                #self.szGeom.find("**/shadow_crack").setAttrib(CullBinAttrib.make("foreground", 10), 10)
                #shs = self.szGeom.findAllMatches("**/*shadow*")
                #for sh in shs:
                #    sh.removeNode()

                # self.szGeom.ls()

                for tree in trees:
                    tree.wrtReparentTo(render)

                self.szGeom.flattenStrong()

                for tree in trees:
                    tree.wrtReparentTo(self.szGeom)

            else:
                loader.loadDNAFile(self.dnaStore, dnaFile)

        self.olc = ZoneUtil.getOutdoorLightingConfig(self.choice.lastHood)
        self.olc.setup()
        self.olc.apply()

        CIGlobals.preRenderScene(render)

        self.asyncSZLoadStatus = True

        #base.accept('l', render.ls)

        if task:
            return task.done

    def enterLoadSZ(self):
        self.loadingDlg = Dialog.GlobalDialog("Loading...")
        self.loadingDlg.show()

        base.cr.renderFrame()
        base.cr.renderFrame()

        self.notify.info("Polling for SZ to load")
        self.asyncSZLoadStatus = False
        self.__async_loadSZTask()

        base.doNextFrame(self.stageFSM.request, ['onStage'])

    def exitLoadSZ(self):
        if hasattr(self, 'loadingDlg'):
            self.loadingDlg.cleanup()
            del self.loadingDlg

    def __changeCamFOV(self, val):
        base.camLens.setMinFov(val / (4. / 3.))

    def enterOnStage(self):
        dna = self.choice.dna
        name = self.choice.name
        self.stageToon.setName(name)
        self.stageToon.setDNAStrand(dna)
        self.stageToon.nametag.setNametagColor(
            NametagGlobals.NametagColors[NametagGlobals.CCLocal])
        self.stageToon.nametag.setActive(0)
        self.stageToon.nametag.nametag3d.request('Rollover')
        self.stageToon.nametag.unmanage(base.marginManager)
        self.stageToon.nametag.updateAll()
        self.stageToon.animFSM.request('neutral')
        self.stageToon.setPosHpr(0, 0, 0, 10, 0, 0)
        self.stageToon.show()

        dat = HOOD_STAGE_DATA[self.choice.lastHood]

        self.stageToonRoot.setPos(dat[2])
        self.stageToonRoot.setHpr(dat[3])

        camera.reparentTo(self.stageToonRoot)

        camera.setPos(dat[0])
        camera.lookAt(self.stageToonRoot, 0, 0, 3)
        startHpr = camera.getHpr()
        camera.setPos(dat[1])
        camera.lookAt(self.stageToonRoot, 0, 0, 3)
        endHpr = camera.getHpr()

        self.camIval = Parallel(
            LerpPosInterval(camera,
                            5.0,
                            dat[1] - (1.6, 0, 0),
                            dat[0] - (1.6, 0, 0),
                            blendType='easeInOut'),
            LerpQuatInterval(camera,
                             5.0,
                             hpr=endHpr,
                             startHpr=startHpr,
                             blendType='easeInOut'),
            LerpFunc(self.__changeCamFOV,
                     duration=5.0,
                     fromData=80.0,
                     toData=CIGlobals.DefaultCameraFov,
                     blendType='easeInOut'))
        if self.isNewToon:
            self.camIval.append(
                Sequence(
                    Func(self.stageToon.hide), Func(base.stopMusic),
                    SoundInterval(self.newToonAnnounceSfx,
                                  startTime=1.674,
                                  duration=4.047),
                    SoundInterval(self.newToonDrumrollSfx),
                    Func(self.stageToon.pose, 'tele',
                         self.stageToon.getNumFrames('tele')),
                    Func(self.newToonAppear), Func(self.stageToon.show),
                    SoundInterval(self.newToonRevealSfx),
                    Func(base.cr.playTheme)))
        else:
            self.camIval.append(
                Sequence(Func(self.showActionButtons),
                         Func(self.enableAllCharButtons), Wait(5.0),
                         Func(self.beginRandomAnims)))

        self.camIval.start()

    def hideActionButtons(self):
        self.playOrCreateButton.hide()
        self.deleteButton.hide()

    def showActionButtons(self):
        self.playOrCreateButton.show()
        self.deleteButton.show()

    def newToonAppear(self):
        self.stopSTAnimSeq()

        self.stAnimSeq = Sequence(
            Func(self.stageToon.animFSM.request, 'teleportIn'), Wait(2.0),
            ActorInterval(self.stageToon, 'wave'),
            Func(self.stageToon.loop, 'neutral'), Func(self.beginRandomAnims),
            Func(self.enableAllCharButtons), Func(self.showActionButtons))
        self.stAnimSeq.start()

    def stopSTAnimSeq(self):
        if self.stAnimSeq:
            self.stAnimSeq.finish()
            self.stAnimSeq = None

    def unloadSZGeom(self):
        if self.szGeom:
            self.szGeom.removeNode()
            self.szGeom = None
        if self.olc:
            self.olc.cleanup()
            self.olc = None

    def beginRandomAnims(self):
        self.stageToon.startLookAround()
        taskMgr.doMethodLater(random.uniform(*ST_ANIM_IVAL),
                              self.__doRandomSTAnim, "doRandomSTAnim")

    def __doRandomSTAnim(self, task):
        anim = random.choice(ST_RANDOM_ANIMS)

        self.stopSTAnimSeq()

        self.stageToon.stopLookAround()

        head = self.stageToon.getPart('head')

        if anim == 'read':
            self.stAnimSeq = Sequence(
                Func(self.stageToon.lerpLookAt, head, (0, -15, 0)),
                Func(self.stageToon.animFSM.request, 'openBook'), Wait(0.5),
                Func(self.stageToon.animFSM.request, 'readBook'), Wait(2.0),
                Func(self.stageToon.lerpLookAt, head, (0, 0, 0)),
                Func(self.stageToon.animFSM.request, 'closeBook'), Wait(1.75),
                Func(self.stageToon.loop, 'neutral'),
                Func(self.stageToon.startLookAround))
        else:
            self.stageToon.lerpLookAt(head, (0, 0, 0))
            self.stAnimSeq = Sequence(ActorInterval(self.stageToon, anim),
                                      Func(self.stageToon.loop, 'neutral'),
                                      Func(self.stageToon.startLookAround))

        self.stAnimSeq.start()

        task.delayTime = random.uniform(*ST_ANIM_IVAL)
        return task.again

    def endRandomAnims(self):
        taskMgr.remove("doRandomSTAnim")
        self.stopSTAnimSeq()

    def exitOnStage(self):
        self.isNewToon = False
        if self.camIval:
            self.camIval.finish()
            self.camIval = None
        self.endRandomAnims()
        self.stopSTAnimSeq()
        camera.reparentTo(render)
        camera.setPosHpr(0, 0, 0, 0, 0, 0)
        #base.transitions.fadeScreen(1.0)
        self.unloadSZGeom()
        self.stageToon.hide()

    def enterCharSelected(self):
        self.playOrCreateButton['text'] = self.PLAY
        self.playOrCreateButton['extraArgs'] = ['play']
        #aspect2d.hide()

    def exitCharSelected(self):
        self.playOrCreateButton.hide()
        self.deleteButton.hide()

    def enterEmptySelected(self):
        self.charNameLabel.setText(self.NO_TOON)
        self.playOrCreateButton['text'] = self.CREATE
        self.playOrCreateButton['extraArgs'] = ['create']
        self.playOrCreateButton.show()

    def exitEmptySelected(self):
        self.playOrCreateButton.hide()

    def __action(self, action):
        for btn in self.charButtons:
            if btn['state'] == DGG.DISABLED:
                self.slot = btn.getPythonTag('slot')
                break
        func = None
        arg = None
        doFade = True
        if action == 'delete':
            func = self.deleteToon
            arg = self.choice.avId
            doFade = False
        elif action == 'play':
            func = self.playGame
            arg = self.choice.slot
        elif action == 'create':
            func = self.enterMAT
        elif action == 'quit':
            func = sys.exit
            doFade = False
        if doFade:
            base.transitions.fadeOut(0.3)
            if arg != None:
                Sequence(Wait(0.31), Func(func, arg)).start()
            else:
                Sequence(Wait(0.31), Func(func)).start()
        else:
            if arg != None:
                func(arg)
            else:
                func()

    def playGame(self, slot):
        messenger.send("avChooseDone",
                       [self.avChooser.getAvChoiceBySlot(slot)])

    def enterMAT(self):
        messenger.send("enterMakeAToon", [self.slot])

    def deleteToon(self, avId):
        # Show a confirmation message
        self.deleteConf = Dialog.GlobalDialog(
            message='This will delete {0} forever.\n\nAre you sure?'.format(
                self.avChooser.getNameFromAvId(avId)),
            style=Dialog.YesNo,
            doneEvent='deleteConfResponse',
            extraArgs=[avId])
        self.acceptOnce('deleteConfResponse', self.__handleDeleteConfResponse)
        self.deleteConf.show()

    def __handleDeleteConfResponse(self, avId):
        doneStatus = self.deleteConf.getValue()
        if doneStatus:
            # Alright, they pressed yes. No complaining to us.
            self.avChooser.avChooseFSM.request("waitForToonDelResponse",
                                               [avId])
        else:
            self.deleteConf.cleanup()
            self.deleteConf = None

    def __handleCharButton(self, slot):
        for btn in self.charButtons:
            if btn.getPythonTag('slot') == slot:
                btn['state'] = DGG.DISABLED
            else:
                btn['state'] = DGG.NORMAL
        if self.avChooser.hasToonInSlot(slot):
            self.choice = self.avChooser.getAvChoiceBySlot(slot)
            self.selectionFSM.request('character')
            self.stageFSM.request('loadSZ')
        else:
            self.selectionFSM.request('empty')
            self.stageFSM.request('off')

    def disableAllCharButtons(self):
        for btn in self.charButtons:
            btn['state'] = DGG.DISABLED

    def enableAllCharButtons(self):
        for btn in self.charButtons:
            if not self.choice or btn.getPythonTag('slot') != self.choice.slot:
                btn['state'] = DGG.NORMAL

    def load(self, newToonSlot=None):
        self.isNewToon = newToonSlot is not None
        self.newToonSlot = newToonSlot

        base.transitions.noTransitions()

        base.cr.renderFrame()
        base.camLens.setMinFov(CIGlobals.DefaultCameraFov / (4. / 3.))

        self.__setupStageToon()

        self.title = DirectLabel(text=self.TITLE,
                                 text_font=CIGlobals.getMickeyFont(),
                                 text_fg=(1, 0.9, 0.1, 1),
                                 relief=None,
                                 text_scale=0.13,
                                 pos=(0, 0, 0.82))
        self.charNameLabel = OnscreenText(text="",
                                          font=CIGlobals.getMickeyFont(),
                                          pos=(-0.25, 0.5, 0),
                                          fg=(1, 0.9, 0.1, 1.0))
        self.charNameLabel.hide()
        self.frame = DirectFrame()
        self.frame['image'] = DGG.getDefaultDialogGeom()
        self.frame['image_color'] = CIGlobals.DialogColor
        self.frame['image_scale'] = (-0.9, -0.9, 0.8)
        self.frame['image_pos'] = (0.82, 0, -0.125)
        self.playOrCreateButton = DirectButton(
            text="",
            pos=(0.8125, 0, -0.35),
            command=self.__action,
            geom=CIGlobals.getDefaultBtnGeom(),
            text_scale=0.06,
            relief=None,
            text_pos=(0, -0.01))
        self.playOrCreateButton.hide()
        self.deleteButton = DirectButton(text="Delete",
                                         pos=(0.8125, 0, -0.45),
                                         command=self.__action,
                                         extraArgs=['delete'],
                                         geom=CIGlobals.getDefaultBtnGeom(),
                                         text_scale=0.06,
                                         relief=None,
                                         text_pos=(0, -0.01))
        self.deleteButton.hide()
        self.quitButton = DirectButton(text="Quit",
                                       pos=(-1.10, 0, -0.925),
                                       command=self.__action,
                                       extraArgs=['quit'],
                                       text_scale=0.06,
                                       geom=CIGlobals.getDefaultBtnGeom(),
                                       relief=None,
                                       text_pos=(0, -0.01))

        for slot in range(6):
            if self.avChooser.hasToonInSlot(slot):
                choice = self.avChooser.getAvChoiceBySlot(slot)
                text = choice.name
            else:
                text = self.NO_TOON
            btn = CIGlobals.makeDefaultScrolledListBtn(
                text=text,
                text_scale=0.06,
                command=self.__handleCharButton,
                extraArgs=[slot])
            btn.setPythonTag('slot', slot)
            self.charButtons.append(btn)
            btn['state'] = DGG.NORMAL

        self.charList = CIGlobals.makeDefaultScrolledList(
            pos=(0.75, 0, -0.225),
            listZorigin=-0.43,
            listFrameSizeZ=0.51,
            arrowButtonScale=0.0,
            items=self.charButtons,
            parent=self.frame)

        if self.isNewToon:
            self.__handleCharButton(self.newToonSlot)
            self.disableAllCharButtons()

    def unload(self):
        self.selectionFSM.requestFinalState()
        self.stageFSM.requestFinalState()
        self.cleanupStageToon()
        self.choice = None
        if self.frame:
            self.frame.destroy()
            self.frame = None
        if self.charButtons:
            for btn in self.charButtons:
                btn.destroy()
            self.charButtons = None
        if self.deleteConf:
            self.deleteConf.cleanup()
            self.deleteConf = None
        if self.charList:
            self.charList.destroy()
            self.charList = None
        if self.charNameLabel:
            self.charNameLabel.destroy()
            self.charNameLabel = None
        if self.playOrCreateButton:
            self.playOrCreateButton.destroy()
            self.playOrCreateButton = None
        if self.deleteButton:
            self.deleteButton.destroy()
            self.deleteButton = None
        if self.quitButton:
            self.quitButton.destroy()
            self.quitButton = None
        if self.title:
            self.title.destroy()
            self.title = None
        base.camera.setPos(0, 0, 0)
        base.camera.setHpr(0, 0, 0)
        base.transitions.noTransitions()
        del self.selectionFSM
class TownLoader(StateData):
    notify = directNotify.newCategory("TownLoader")

    def __init__(self, hood, parentFSMState, doneEvent):
        self.hood = hood
        self.parentFSMState = parentFSMState
        StateData.__init__(self, doneEvent)
        self.fsm = ClassicFSM('TownLoader', [
            State('start', self.enterStart, self.exitStart,
                  ['quietZone', 'street', 'toonInterior']),
            State('street', self.enterStreet, self.exitStreet, ['quietZone']),
            State('toonInterior', self.enterToonInterior,
                  self.exitToonInterior, ['quietZone']),
            State('suitInterior', self.enterSuitInterior,
                  self.exitSuitInterior, ['quietZone']),
            State('quietZone', self.enterQuietZone, self.exitQuietZone,
                  ['street', 'toonInterior', 'suitInterior']),
            State('final', self.enterFinal, self.exitFinal, ['start'])
        ], 'start', 'final')
        self.branchZone = None
        self.canonicalBranchZone = None
        self.placeDoneEvent = 'placeDone'
        self.streetSong = ''
        self.interiorSong = ''
        self.linkTunnels = []
        self.place = None
        return

    def findAndMakeLinkTunnels(self, requestStatus):
        for tunnel in self.geom.findAllMatches('**/*linktunnel*'):
            dnaRootStr = tunnel.getName()
            zone = LinkTunnel.getZoneFromDNARootStr(dnaRootStr)
            zone = LinkTunnel.maybeFixZone(zone)
            tunnelClass = LinkTunnel.getRecommendedTunnelClassFromZone(zone)
            link = tunnelClass(tunnel, dnaRootStr)
            self.linkTunnels.append(link)

    def load(self, zoneId):
        StateData.load(self)
        self.zoneId = zoneId
        self.branchZone = ZoneUtil.getBranchZone(zoneId)
        self.canonicalBranchZone = ZoneUtil.getCanonicalBranchZone(zoneId)

    def unload(self):
        self.parentFSMState.removeChild(self.fsm)
        del self.parentFSMState
        del self.fsm
        del self.streetClass
        base.disablePhysicsNodes(self.landmarkBlocks)
        self.landmarkBlocks.removeNode()
        del self.landmarkBlocks
        self.hood.dnaStore.resetSuitPoints()
        self.hood.dnaStore.resetBattleCells()
        del self.hood
        del self.nodeDict
        del self.zoneDict
        del self.fadeInDict
        del self.fadeOutDict
        del self.nodeList
        del self.zoneVisDict

        base.disablePhysicsNodes(self.geom)
        self.geom.removeNode()
        del self.geom
        del self.streetSong
        del self.interiorSong

        #CIGlobals.doSceneCleanup()

        StateData.unload(self)

    def enter(self, requestStatus):
        StateData.enter(self)
        self.findAndMakeLinkTunnels(requestStatus)
        self.fsm.enterInitialState()
        self.setState(requestStatus['where'], requestStatus)

    def exit(self):
        self.fsm.requestFinalState()
        self.ignoreAll()
        ModelPool.garbageCollect()
        TexturePool.garbageCollect()
        StateData.exit(self)

    def setState(self, state, requestStatus):
        self.fsm.request(state, [requestStatus])

    def enterStart(self):
        pass

    def exitStart(self):
        pass

    def enterStreet(self, requestStatus):
        self.acceptOnce(self.placeDoneEvent, self.streetDone)
        self.place = self.streetClass(self, self.fsm, self.placeDoneEvent)
        self.place.load()

    def exitStreet(self):
        self.ignore(self.placeDoneEvent)
        self.place.exit()
        self.place.unload()
        self.place = None
        base.cr.playGame.setPlace(self.place)
        return

    def streetDone(self):
        self.requestStatus = self.place.doneStatus
        status = self.place.doneStatus
        if (status['loader'] == 'townLoader'
                and ZoneUtil.getBranchZone(status['zoneId']) == self.branchZone
                and status['shardId'] is None or status['how'] == 'doorOut'
                or status['where'] == 'suitInterior'):
            self.fsm.request('quietZone', [status])
        else:
            self.doneStatus = status
            messenger.send(self.doneEvent)

    def enterToonInterior(self, requestStatus):
        self.acceptOnce(self.placeDoneEvent, self.handleToonInteriorDone)
        self.place = ToonInterior.ToonInterior(self, self.fsm,
                                               self.placeDoneEvent)
        self.place.load()

    def exitToonInterior(self):
        self.ignore(self.placeDoneEvent)
        self.place.exit()
        self.place.unload()
        self.place = None
        base.cr.playGame.setPlace(self.place)
        return

    def enterSuitInterior(self, requestStatus):
        self.acceptOnce(self.placeDoneEvent, self.handleSuitInteriorDone)
        self.place = CogOfficeInterior.CogOfficeInterior(
            self, self.fsm, self.placeDoneEvent)
        self.place.load()

    def exitSuitInterior(self):
        self.ignore(self.placeDoneEvent)
        self.place.exit()
        self.place.unload()
        self.place = None
        base.cr.playGame.setPlace(self.place)

    def enterThePlace(self, requestStatus):
        base.cr.playGame.setPlace(self.place)
        if self.place is not None:
            self.place.enter(requestStatus)

    def handleToonInteriorDone(self):
        status = self.place.doneStatus
        if (status['loader'] == 'townLoader'
                and ZoneUtil.getBranchZone(status['zoneId']) == self.branchZone
                and status['shardId'] is None or status['how'] == 'doorOut'):
            self.fsm.request('quietZone', [status])
        else:
            self.doneStatus = status
            messenger.send(self.doneEvent)
        return

    def handleSuitInteriorDone(self):
        self.handleToonInteriorDone()

    def enterQuietZone(self, requestStatus):
        self.fsm.request(requestStatus['where'], [requestStatus],
                         exitCurrent=0)

        self.quietZoneDoneEvent = uniqueName('quietZoneDone')
        self.acceptOnce(self.quietZoneDoneEvent, self.handleQuietZoneDone)
        self.quietZoneStateData = QuietZoneState(self.quietZoneDoneEvent)
        self.quietZoneStateData.load()
        self.quietZoneStateData.enter(requestStatus)

    def exitQuietZone(self):
        self.ignore(self.quietZoneDoneEvent)
        del self.quietZoneDoneEvent
        self.quietZoneStateData.exit()
        self.quietZoneStateData.unload()
        self.quietZoneStateData = None
        return

    def handleQuietZoneDone(self):
        status = self.quietZoneStateData.getRequestStatus()
        self.exitQuietZone()
        self.enterThePlace(status)

    def enterFinal(self):
        pass

    def exitFinal(self):
        pass

    def createHood(self, dnaFile, loadStorage=1, flattenNow=True):
        if loadStorage:
            loader.loadDNAFile(self.hood.dnaStore,
                               'phase_5/dna/storage_town.pdna')
            loader.loadDNAFile(self.hood.dnaStore, self.townStorageDNAFile)
        node = loader.loadDNAFile(self.hood.dnaStore, dnaFile)
        if node.getNumParents() == 1:
            self.geom = NodePath(node.getParent(0))
            self.geom.reparentTo(hidden)
        else:
            self.geom = hidden.attachNewNode(node)
        if flattenNow:
            self.doFlatten()
        self.geom.setName('town_top_level')

    def doFlatten(self):
        self.makeDictionaries(self.hood.dnaStore)
        self.reparentLandmarkBlockNodes()
        base.createPhysicsNodes(self.geom)
        self.renameFloorPolys(self.nodeList)
        self.geom.flattenLight()

        CIGlobals.preRenderScene(self.geom)

    def reparentLandmarkBlockNodes(self):
        bucket = self.landmarkBlocks = hidden.attachNewNode('landmarkBlocks')
        npc = self.geom.findAllMatches('**/sb*:*_landmark_*_DNARoot')
        for i in xrange(npc.getNumPaths()):
            nodePath = npc.getPath(i)
            nodePath.wrtReparentTo(bucket)

        npc = self.geom.findAllMatches('**/sb*:*animated_building*_DNARoot')
        for i in xrange(npc.getNumPaths()):
            nodePath = npc.getPath(i)
            nodePath.wrtReparentTo(bucket)

    def makeDictionaries(self, dnaStore):
        self.nodeDict = {}
        self.zoneDict = {}
        self.zoneVisDict = {}
        self.nodeList = []
        self.fadeInDict = {}
        self.fadeOutDict = {}
        a1 = Vec4(1, 1, 1, 1)
        a0 = Vec4(1, 1, 1, 0)
        numVisGroups = dnaStore.getNumDNAVisGroupsAI()
        for i in xrange(numVisGroups):
            groupFullName = dnaStore.getDNAVisGroupName(i)
            visGroup = dnaStore.getDNAVisGroupAI(i)
            groupName = base.cr.hoodMgr.extractGroupName(groupFullName)
            zoneId = int(groupName)
            zoneId = ZoneUtil.getTrueZoneId(zoneId, self.zoneId)
            groupNode = self.geom.find('**/' + groupFullName)
            if groupNode.isEmpty():
                continue
            else:
                if ':' in groupName:
                    groupName = '%s%s' % (zoneId,
                                          groupName[groupName.index(':'):])
                else:
                    groupName = '%s' % zoneId
                groupNode.setName(groupName)

            CIGlobals.replaceDecalEffectsWithDepthOffsetAttrib(groupNode)

            #group all the flat walls

            block2flatwall = {}
            flatwalls = groupNode.findAllMatches("**/tb*:*_DNARoot;+s")
            for flatwall in flatwalls:
                if "toon_landmark" in flatwall.getName():
                    print "Skipping", flatwall.getName()
                    continue
                if flatwall.hasTag("DNACode") and flatwall.hasMat():
                    continue
                block = int(flatwall.getName().split(":")[0][2:])
                if not block2flatwall.has_key(block):
                    block2flatwall[block] = groupNode.attachNewNode(
                        ModelNode('toonBuildingsBlock' + str(block)))
                flatwall.wrtReparentTo(block2flatwall[block])

            for node in block2flatwall.values():
                for child in node.findAllMatches("**"):
                    child.clearEffect(DecalEffect.getClassType())
                    child.clearTag("DNACode")
                    child.clearTag("cam")
                CIGlobals.clearModelNodesBelow(node)
                node.flattenStrong()

            flattenGroup = groupNode.attachNewNode('optim')
            flattens = ['street*_DNARoot']
            removes = ['interactive_prop*_DNARoot']
            for remove in removes:
                for np in groupNode.findAllMatches("**/" + remove):
                    np.removeNode()
            for flatten in flattens:
                for np in groupNode.findAllMatches("**/" + flatten):
                    if np.hasTag("DNACode") and np.hasMat():
                        continue
                    for child in np.findAllMatches("**"):
                        child.clearEffect(DecalEffect.getClassType())
                        child.clearTag("DNACode")
                        child.clearTag("cam")
                    np.wrtReparentTo(flattenGroup)
            flattenGroup.clearModelNodes()
            flattenGroup.flattenStrong()

            CIGlobals.flattenModelNodes(groupNode)
            groupNode.flattenStrong()
            #groupNode.ls()

            self.nodeDict[zoneId] = []
            self.nodeList.append(groupNode)
            self.zoneDict[zoneId] = groupNode
            visibles = []
            for i in xrange(visGroup.getNumVisibles()):
                visibles.append(int(visGroup.get_visible(i)))
            visibles.append(ZoneUtil.getBranchZone(zoneId))
            self.zoneVisDict[zoneId] = visibles
            fadeDuration = 0.5
            self.fadeOutDict[groupNode] = Sequence(
                Func(groupNode.setTransparency, 1),
                LerpColorScaleInterval(groupNode,
                                       fadeDuration,
                                       a0,
                                       startColorScale=a1),
                Func(groupNode.clearColorScale),
                Func(groupNode.clearTransparency),
                Func(groupNode.stash),
                Func(base.disablePhysicsNodes, groupNode),
                name='fadeZone-' + str(zoneId),
                autoPause=1)
            self.fadeInDict[groupNode] = Sequence(
                Func(base.enablePhysicsNodes, groupNode),
                Func(groupNode.unstash),
                Func(groupNode.setTransparency, 1),
                LerpColorScaleInterval(groupNode,
                                       fadeDuration,
                                       a1,
                                       startColorScale=a0),
                Func(groupNode.clearColorScale),
                Func(groupNode.clearTransparency),
                name='fadeZone-' + str(zoneId),
                autoPause=1)

        for i in xrange(numVisGroups):
            groupFullName = dnaStore.getDNAVisGroupName(i)
            zoneId = int(base.cr.hoodMgr.extractGroupName(groupFullName))
            zoneId = ZoneUtil.getTrueZoneId(zoneId, self.zoneId)
            for j in xrange(dnaStore.getNumVisiblesInDNAVisGroup(i)):
                visName = dnaStore.getVisibleName(i, j)
                groupName = base.cr.hoodMgr.extractGroupName(visName)
                nextZoneId = int(groupName)
                nextZoneId = ZoneUtil.getTrueZoneId(nextZoneId, self.zoneId)
                visNode = self.zoneDict[nextZoneId]
                self.nodeDict[zoneId].append(visNode)

        self.hood.dnaStore.resetPlaceNodes()
        self.hood.dnaStore.resetDNAGroups()
        self.hood.dnaStore.resetDNAVisGroups()
        self.hood.dnaStore.resetDNAVisGroupsAI()

    def renameFloorPolys(self, nodeList):
        for i in nodeList:
            collNodePaths = i.findAllMatches('**/+BulletRigidBodyNode')
            numCollNodePaths = collNodePaths.getNumPaths()
            visGroupName = i.node().getName()
            for j in xrange(numCollNodePaths):
                collNodePath = collNodePaths.getPath(j)
                bitMask = collNodePath.node().getIntoCollideMask()
                if bitMask == CIGlobals.FloorGroup:
                    collNodePath.node().setName(visGroupName)
                    collNodePath.setCollideMask(CIGlobals.StreetVisGroup)
Exemple #11
0
class cMinicolumn:
    def __init__(self, nameOfLayer, nOfCellsPerColumn):
        self.cells = []
        self.nOfCellsPerColumn = nOfCellsPerColumn

        for i in range(nOfCellsPerColumn):
            n = cCell(self)
            self.cells.append(n)

        self.idx = -1
        self.parentLayer = nameOfLayer
        self.transparency = 1.0
        self.gfxCreated = False
        self.LodDistance1Stored = 100.0
        self.LodDistance2Stored = 5000.0

        self.bursting = False
        self.active = False
        self.oneOfCellPredictive = False
        self.oneOfCellCorrectlyPredicted = False
        self.oneOfCellFalselyPredicted = False

        self.cntSegments = None  # info about how many segments are on this cell

    def CreateGfx(self, loader, idx):
        #                __node
        #                /   \
        #  cellsNodePath   columnBox

        self.lod = LODNode("columnLOD")  # Level of detail node for Column
        self.__node = NodePath(
            self.lod
        )  # NodePath(PandaNode('column'))# loader.loadModel("models/box")
        self.__node.setPos(0, 0, 0)
        self.__node.setScale(1, 1, 1)

        self.idx = idx

        # self.__node.setTag('clickable',str(idx))#to be able to click on it

        self.__columnBox = loader.loadModel(
            os.path.join(os.getcwd(), "models/cube"))
        self.__columnBox.setPos(
            0, 0, -0.5 + (0 if len(self.cells) == 0 else len(self.cells) *
                          (1 + CELL_OFFSET) / 2))
        self.__columnBox.setScale(
            0.5, 0.5, 0.5 * (1 if len(self.cells) == 0 else len(self.cells) *
                             (1 + CELL_OFFSET)))
        self.__columnBox.setName("columnBox")

        self.__cellsNodePath = NodePath(
            PandaNode("cellsNode"))  # to pack all cells into one node path
        self.__cellsNodePath.setName("column")
        self.__cellsNodePath.setTag(
            "id",
            str(idx))  # to be able to retrieve index of column for mouse click

        self.lod.addSwitch(100.0, 0.0)
        self.lod.addSwitch(5000.0, 100.0)

        self.__cellsNodePath.reparentTo(self.__node)
        self.__columnBox.reparentTo(self.__node)

        z = 0
        idx = 0
        for n in self.cells:
            n.CreateGfx(loader, idx)
            idx += 1
            n.getNode().setPos(0, 0, z)
            z += 1 + CELL_OFFSET
            n.getNode().reparentTo(self.__cellsNodePath)

        self.gfxCreated = True

    def LODUpdateSwitch(self, lodDistance, lodDistance2):
        self.lodDistance1Stored = lodDistance
        self.lodDistance2Stored = lodDistance2

        self.lod.clearSwitches()
        self.lod.addSwitch(lodDistance, 0.0)
        self.lod.addSwitch(lodDistance2, lodDistance)

    def LODUpdateSwitch_long(self):
        self.lod.clearSwitches()
        self.lod.addSwitch(self.lodDistance2Stored, 0.0)
        self.lod.addSwitch(self.lodDistance2Stored, self.lodDistance2Stored)

    def LODUpdateSwitch_normal(self):
        self.LODUpdateSwitch(self.lodDistance1Stored, self.lodDistance2Stored)

    def UpdateState(self, bursting, activeColumn, oneOfCellPredictive,
                    oneOfCellCorrectlyPredicted, oneOfCellFalselyPredicted):

        self.bursting = bursting
        self.active = activeColumn
        self.oneOfCellPredictive = oneOfCellPredictive
        self.oneOfCellCorrectlyPredicted = oneOfCellCorrectlyPredicted
        self.oneOfCellFalselyPredicted = oneOfCellFalselyPredicted

        # update column box color (for LOD in distance look)
        if self.oneOfCellCorrectlyPredicted:
            COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED
            self.__columnBox.setColor(col)
        elif self.oneOfCellFalselyPredicted:
            COL_COLUMN_ONEOFCELLFALSELY_PREDICTED.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLFALSELY_PREDICTED
            self.__columnBox.setColor(col)
        elif self.bursting:
            COL_COLUMN_BURSTING.setW(self.transparency)
            col = COL_COLUMN_BURSTING
            self.__columnBox.setColor(col)
        elif self.active and self.oneOfCellPredictive:
            COL_COLUMN_ACTIVE_AND_ONEOFCELLPREDICTIVE.setW(self.transparency)
            col = COL_COLUMN_ACTIVE_AND_ONEOFCELLPREDICTIVE
            self.__columnBox.setColor(col)
        elif self.oneOfCellPredictive:
            COL_COLUMN_ONEOFCELLPREDICTIVE.setW(self.transparency)
            col = COL_COLUMN_ONEOFCELLPREDICTIVE
            self.__columnBox.setColor(col)
        elif self.active:
            COL_COLUMN_ACTIVE.setW(self.transparency)
            col = COL_COLUMN_ACTIVE
            self.__columnBox.setColor(col)

        else:
            COL_COLUMN_INACTIVE.setW(self.transparency)
            col = COL_COLUMN_INACTIVE
            self.__columnBox.setColor(col)

#        for n in self.cells:
#            n.active = active
#            n.UpdateState()

    def getNode(self):
        return self.__node

    def updateWireframe(self, value):
        for cell in self.cells:
            cell.updateWireframe(value)
        if value:
            self.__columnBox.setRenderModeFilledWireframe(LColor(0, 0, 0, 1.0))
        else:
            self.__columnBox.setRenderModeFilled()

    def CreateSynapses(self, regionObjects, columnConnections, synapsesType,
                       sourceRegions, activeOnly):
        printLog("Creating synapses", verbosityMedium)

        ConnectionFactory.CreateSynapses(
            callbackCreateSynapse=self._CreateOneSynapse,
            synapsesType=synapsesType,
            regionObjects=regionObjects,
            connections=columnConnections,
            idx=self.idx,
            sourceRegions=sourceRegions,
            activeOnly=activeOnly)

        for arr in columnConnections:
            if arr[0] == self.idx:  # find this column
                self.cntSegments = len(arr[1])
                break

    # creates synapse, that will go from presynCell to this cell
    # presynCell - cell object
    def _CreateOneSynapse(self, presynCell, synapsesType):

        form = GeomVertexFormat.getV3()
        vdata = GeomVertexData("SynapseLine", form, Geom.UHStatic)
        vdata.setNumRows(1)
        vertex = GeomVertexWriter(vdata, "vertex")

        vertex.addData3f(presynCell.getNode().getPos(self.__node))
        vertex.addData3f(0, 0, 0)
        # vertex.addData3f(self.__node.getPos())
        # printLog("inputObj:"+str(i)+"bits:"+str(y))
        # printLog(inputObj[i].inputBits[y].getNode().getPos(self.__node))

        prim = GeomLines(Geom.UHStatic)
        prim.addVertices(0, 1)

        geom = Geom(vdata)
        geom.addPrimitive(prim)

        node = GeomNode("Synapse_" + str(synapsesType))
        node.addGeom(geom)

        nodePath = self.__cellsNodePath.attachNewNode(node)

        nodePath.setRenderModeThickness(2)
        # color of the line
        if presynCell.active:
            nodePath.setColor(COL_PROXIMAL_SYNAPSES_ACTIVE)
        else:
            nodePath.setColor(COL_PROXIMAL_SYNAPSES_INACTIVE)

    def setTransparency(self, transparency):
        self.transparency = transparency
        for cell in self.cells:
            cell.setTransparency(transparency)

        self.UpdateState(self.bursting, self.active, self.oneOfCellPredictive,
                         self.oneOfCellCorrectlyPredicted,
                         self.oneOfCellFalselyPredicted)

    def DestroySynapses(self, synapseType=None):
        if synapseType is None:
            synapseType = '*'

        if not self.gfxCreated:
            return

        if synapseType == 'proximal':
            for syn in self.__cellsNodePath.findAllMatches("Synapse_" +
                                                           str(synapseType)):
                syn.removeNode()

        for cell in self.cells:
            cell.DestroySynapses(synapseType)

    def getDescription(self):
        txt = ""
        txt += "ID:" + str(self.idx) + "\n"
        txt += "Active:" + str(self.active) + "\n"
        txt += "One of cell is predictive:" + str(
            self.oneOfCellPredictive) + "\n"
        txt += "One of cell correctly predicted:" + str(
            self.oneOfCellCorrectlyPredicted) + "\n"
        txt += "One of cell false predicted:" + str(
            self.oneOfCellFalselyPredicted) + "\n"
        txt += "Number of segments:" + str(self.cntSegments) + "\n"
        return txt
Exemple #12
0
class CogdoMazeFactory:

    def __init__(self, randomNumGen, width, height, frameWallThickness = Globals.FrameWallThickness, cogdoMazeData = CogdoMazeData):
        self._rng = RandomNumGen(randomNumGen)
        self.width = width
        self.height = height
        self.frameWallThickness = frameWallThickness
        self._cogdoMazeData = cogdoMazeData
        self.quadrantSize = self._cogdoMazeData.QuadrantSize
        self.cellWidth = self._cogdoMazeData.QuadrantCellWidth

    def getMazeData(self):
        if not hasattr(self, '_data'):
            self._generateMazeData()
        return self._data

    def createCogdoMaze(self, flattenModel = True):
        if not hasattr(self, '_maze'):
            self._loadAndBuildMazeModel(flatten=flattenModel)
        return CogdoMaze(self._model, self._data, self.cellWidth)

    def _gatherQuadrantData(self):
        self.openBarriers = []
        barrierItems = range(Globals.TotalBarriers)
        self._rng.shuffle(barrierItems)
        for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]:
            self.openBarriers.append(i)

        self.quadrantData = []
        quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys()
        self._rng.shuffle(quadrantKeys)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                key = quadrantKeys[i]
                collTable = self._cogdoMazeData.QuadrantCollisions[key]
                angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)]
                self.quadrantData.append((key, collTable[angle], angle))
                i += 1
                if x * y >= self._cogdoMazeData.NumQuadrants:
                    i = 0

    def _generateBarrierData(self):
        data = []
        for y in xrange(self.height):
            data.append([])
            for x in xrange(self.width):
                if x == self.width - 1:
                    ax = -1
                else:
                    ax = 1
                if y == self.height - 1:
                    ay = -1
                else:
                    ay = 1
                data[y].append([ax, ay])

        dirUp = 0
        dirDown = 1
        dirLeft = 2
        dirRight = 3

        def getAvailableDirections(ax, ay, ignore = None):
            dirs = []
            if ax - 1 >= 0 and data[ay][ax - 1][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirLeft)
            if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirRight)
            if ay - 1 >= 0 and data[ay - 1][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirDown)
            if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirUp)
            return dirs

        visited = []

        def tryVisitNeighbor(ax, ay, ad):
            if ad == dirUp:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_TOP] = 0
                ay += 1
            elif ad == dirDown:
                if data[ay - 1][ax] in visited:
                    return None
                visited.append(data[ay - 1][ax])
                data[ay - 1][ax][BARRIER_DATA_TOP] = 0
                ay -= 1
            elif ad == dirLeft:
                if data[ay][ax - 1] in visited:
                    return None
                visited.append(data[ay][ax - 1])
                data[ay][ax - 1][BARRIER_DATA_RIGHT] = 0
                ax -= 1
            elif ad == dirRight:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_RIGHT] = 0
                ax += 1
            return (ax, ay)

        def openBarriers(x, y):
            dirs = getAvailableDirections(x, y)
            for dir in dirs:
                next = tryVisitNeighbor(x, y, dir)
                if next is not None:
                    openBarriers(*next)

            return

        x = self._rng.randint(0, self.width - 1)
        y = self._rng.randint(0, self.height - 1)
        openBarriers(x, y)
        self._barrierData = data
        return

    def _generateMazeData(self):
        if not hasattr(self, 'quadrantData'):
            self._gatherQuadrantData()
        self._data = {}
        self._data['width'] = (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize
        self._data['height'] = (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize
        self._data['originX'] = int(self._data['width'] / 2)
        self._data['originY'] = int(self._data['height'] / 2)
        collisionTable = []
        horizontalWall = [ 1 for x in xrange(self._data['width']) ]
        collisionTable.append(horizontalWall)
        for i in xrange(0, len(self.quadrantData), self.width):
            for y in xrange(self.quadrantSize):
                row = [1]
                for x in xrange(i, i + self.width):
                    if x == 1 and y < self.quadrantSize / 2 - 2:
                        newData = []
                        for j in self.quadrantData[x][1][y]:
                            if j == 0:
                                newData.append(2)
                            else:
                                newData.append(j + 0)

                        row += newData + [1]
                    else:
                        row += self.quadrantData[x][1][y] + [1]

                collisionTable.append(row)

            collisionTable.append(horizontalWall[:])

        barriers = Globals.MazeBarriers
        for i in xrange(len(barriers)):
            for coords in barriers[i]:
                collisionTable[coords[1]][coords[0]] = 0

        y = self._data['originY']
        for x in xrange(len(collisionTable[y])):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        x = self._data['originX']
        for y in xrange(len(collisionTable)):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        self._data['collisionTable'] = collisionTable

    def _loadAndBuildMazeModel(self, flatten = False):
        self.getMazeData()
        self._model = NodePath('CogdoMazeModel')
        levelModel = CogdoUtil.loadMazeModel('level')
        self.quadrants = []
        quadrantUnitSize = int(self.quadrantSize * self.cellWidth)
        frameActualSize = self.frameWallThickness * self.cellWidth
        size = quadrantUnitSize + frameActualSize
        halfWidth = int(self.width / 2)
        halfHeight = int(self.height / 2)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                ax = (x - halfWidth) * size
                ay = (y - halfHeight) * size
                extension = ''
                if hasattr(getBase(), 'air'):
                    extension = '.bam'
                filepath = self.quadrantData[i][0] + extension
                angle = self.quadrantData[i][2]
                m = self._createQuadrant(filepath, i, angle, quadrantUnitSize)
                m.setPos(ax, ay, 0)
                m.reparentTo(self._model)
                self.quadrants.append(m)
                i += 1

        quadrantHalfUnitSize = quadrantUnitSize * 0.5
        barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider')
        y = 3
        for x in xrange(self.width):
            if x == (self.width - 1) / 2:
                continue
            ax = (x - halfWidth) * size
            ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5)
            b = NodePath('barrier')
            barrierModel.instanceTo(b)
            b.setPos(ax, ay, 0)
            b.reparentTo(self._model)

        offset = self.cellWidth - 0.5
        for x in (0, 3):
            for y in xrange(self.height):
                ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset
                ay = (y - halfHeight) * size
                b = NodePath('barrier')
                barrierModel.instanceTo(b)
                b.setPos(ax, ay, 0)
                b.setH(90)
                b.reparentTo(self._model)

            offset -= 2.0

        barrierModel.removeNode()
        levelModel.getChildren().reparentTo(self._model)
        for np in self._model.findAllMatches('**/*lightCone*'):
            CogdoUtil.initializeLightCone(np, 'fixed', 3)

        if flatten:
            self._model.flattenStrong()
        return self._model

    def _createQuadrant(self, filepath, serialNum, angle, size):
        root = NodePath('QuadrantRoot-%i' % serialNum)
        quadrant = loader.loadModel(filepath)
        quadrant.getChildren().reparentTo(root)
        root.setH(angle)
        return root
class GunGameLevelLoader:
    notify = directNotify.newCategory("GunGameLevelLoader")

    LevelData = {
        # momada means: Mix Of Mint And District Attorney's
        'momada': {
            'name': ZoneUtil.ToonBattleOriginalLevel,
            'camera': (Point3(0.0, -25.80, 7.59), Vec3(0.00, 0.00, 0.00)),
            'models': [
                "phase_11/models/lawbotHQ/LB_Zone03a.bam",
                "phase_11/models/lawbotHQ/LB_Zone04a.bam",
                "phase_11/models/lawbotHQ/LB_Zone7av2.bam",
                "phase_11/models/lawbotHQ/LB_Zone08a.bam",
                "phase_11/models/lawbotHQ/LB_Zone13a.bam",
                "phase_10/models/cashbotHQ/ZONE17a.bam",
                "phase_10/models/cashbotHQ/ZONE18a.bam",
                "phase_11/models/lawbotHQ/LB_Zone22a.bam"
            ],
            'parents': [
                render,
                "EXIT",
                "EXIT",
                "EXIT",
                "ENTRANCE",
                "ENTRANCE",
                "ENTRANCE",
                "EXIT"
            ],
            'model_positions': [
                Point3(0.00, 0.00, 0.00),
                Point3(-1.02, 59.73, 0.00),
                Point3(0.00, 74.77, 0.00),
                Point3(0.00, 89.37, -13.50),
                Point3(16.33, -136.53, 0.00),
                Point3(-1.01, -104.40, 0.00),
                Point3(0.65, -23.86, 0.00),
                Point3(-55.66, -29.01, 0.00)
            ],
            'model_orientations': [
                Vec3(0.00, 0.00, 0.00),
                Vec3(0.00, 0.00, 0.00),
                Vec3(90.00, 0.00, 0.00),
                Vec3(180.00, 0.00, 0.00),
                Vec3(97.00, 0.00, 0.00),
                Vec3(359.95, 0.00, 0.00),
                Vec3(90.00, 0.00, 0.00),
                Vec3(270.00, 0.00, 0.00)
            ],
            'spawn_points': [
                (Point3(0, 0, 0), Vec3(0, 0, 0)),
                (Point3(-20, 50, 0), Vec3(0, 0, 0)),
                (Point3(20, 50, 0), Vec3(0, 0, 0)),
                (Point3(0, 120, 0), Vec3(0, 0, 0)),
                (Point3(0, 100, 0), Vec3(180, 0, 0)),
                (Point3(-90, 0, 0), Vec3(0, 0, 0)),
                (Point3(-170, 0, 0), Vec3(0, 0, 0)),
                (Point3(-90, 50, 0), Vec3(0, 0, 0)),
                (Point3(-170, 50, 0), Vec3(0, 0, 0)),
                (Point3(35, 250, 0), Vec3(-90, 0, 0)),
                (Point3(0, 285, 0), Vec3(180, 0, 0)),
                (Point3(-185, 250, 0), Vec3(90, 0, 0))
            ]
        },

        'dg': {
            'name': ZoneUtil.DaisyGardens,
            'camera': (Point3(-33.13, -3.20, 48.62), Vec3(326.31, 332.68, 0.00)),
            'dna': [
                'phase_8/dna/storage_DG.pdna',
                'phase_8/dna/storage_DG_sz.pdna',
                'phase_8/dna/daisys_garden_sz.pdna'
            ],
            'sky': 'TT',
            'spawn_points': hoodMgr.dropPoints[ZoneUtil.DaisyGardens]
        },

        'mml': {
            'name': ZoneUtil.MinniesMelodyland,
            'camera': (Point3(-54.42, -91.05, 34.89), Vec3(315.29, 336.80, 0.00)),
            'dna': [
                'phase_6/dna/storage_MM.pdna',
                'phase_6/dna/storage_MM_sz.pdna',
                'phase_6/dna/minnies_melody_land_sz.pdna'
            ],
            'sky': 'MM',
            'spawn_points': hoodMgr.dropPoints[ZoneUtil.MinniesMelodyland]
        },

        'oz': {
            'name': ZoneUtil.OutdoorZone,
            'camera': (Point3(-54.42, -91.05, 34.89), Vec3(315.29, 336.80, 0.00)),
            'dna': [
                'phase_6/dna/storage_OZ.pdna',
                'phase_6/dna/storage_OZ_sz.pdna',
                'phase_6/dna/outdoor_zone_sz.pdna'
            ],
            'sky': 'TT',
            'spawn_points': hoodMgr.dropPoints[ZoneUtil.OutdoorZone]
        },

        'cbhq': {
            'name': ZoneUtil.CashbotHQ,
            'camera': (Point3(302.64, 5.00, 15.20), Vec3(135.00, 341.57, 0.00)),
            'model': 'phase_10/models/cogHQ/CashBotShippingStation.bam',
            'sky': None,
            'spawn_points': hoodMgr.dropPoints[ZoneUtil.CashbotHQ]
        },

        'sbf': {
            'name': ZoneUtil.SellbotFactory,
            'camera': (Point3(0, 0, 0), Vec3(0, 0, 0)),
            'model': "phase_9/models/cogHQ/SelbotLegFactory.bam",
            'sky': 'cog',
            'sky_scale': 10.0,
            'occluders': 'phase_9/models/cogHQ/factory_sneak_occluders.egg',
            'spawn_points': {GGG.Teams.BLUE: [
                    (Point3(13, 30, 3.73), Point3(0, 0, 0)), (Point3(21, 30, 3.73), Point3(0, 0, 0)), (Point3(29, 30, 3.73), Point3(0, 0, 0)),
                    (Point3(13, 20, 3.73), Point3(0, 0, 0)), (Point3(21, 20, 3.73), Point3(0, 0, 0)), (Point3(29, 30, 3.73), Point3(0, 0, 0))],
                GGG.Teams.RED: [
                    (Point3(-644.43, 378.12, 8.73), Point3(270, 0, 0)), (Point3(-644.43, 370.75, 8.73), Point3(270, 0, 0)), (Point3(-644.43, 363.22, 8.73), Point3(270, 0, 0)),
                    (Point3(-659.05, 378.12, 8.73), Point3(270, 0, 0)), (Point3(-659.05, 370.75, 8.73), Point3(270, 0, 0)), (Point3(-659.05, 363.22, 8.73), Point3(270, 0, 0))]
            },
            'flag_points': {GGG.Teams.BLUE: [Point3(213.23, 340.59, 19.73), Point3(90, 0, 0)],
                GGG.Teams.RED: [Point3(-543.60, 595.79, 9.73), Point3(270, 0, 0)]},
            'flagpoint_points': {GGG.Teams.BLUE: [Point3(-543.60, 595.79, 9.73), Point3(270, 0, 0)],
                GGG.Teams.RED: [Point3(213.23, 340.59, 19.73), Point3(0, 0, 0)]}
        },
        
        'ttc' : {
            'name' : ZoneUtil.ToontownCentral,
            'dna' : [
                'phase_4/dna/storage_TT.pdna',
                'phase_4/dna/storage_TT_sz.pdna',
                'phase_4/dna/new_ttc_sz.pdna',
            ],
            'sky' : 'TT',
            'spawn_points' : [
                (9.90324401855, 91.9139556885, 8.0364112854, -545.909545898, 0.0, 0.0),
                (77.9181442261, 50.953086853, 7.52815723419, -598.509460449, 0.0, 0.0),
                (93.7379760742, 6.37303066254, 7.99749088287, -626.209533691, 0.0, 0.0),
                (39.0383415222, -81.5989837646, 8.01874637604, -694.309265137, 0.0, 0.0),
                (-19.2093048096, -95.1359481812, 8.07303524017,  -731.409240723, 0.0, 0.0),
                (-84.4093933105, -45.4780502319, 8.06541728973, -781.809143066, 0.0, 0.0),
                (-92.2512283325, 2.41426730156, 8.03108692169, -811.70916748, 0.0, 0.0),
                (46.8868179321, 81.3593673706, 8.04793071747, -955.309509277, 0.0, 0.0),
                (32.3203735352, 90.0017929077, 8.06353855133, -884.409301758, 0.0, 0.0)
            ],
            'cap_point' : Point3(-1.5, 0, 0)
        }
    }

    SkyData = {
        'TT': 'phase_3.5/models/props',
        'MM': 'phase_6/models/props',
        'cog': 'phase_9/models/cogHQ',

        'MovingSkies': ['TT']
    }

    def __init__(self, mg):
        self.mg = mg
        self.levelName = None
        self.dnaStore = DNAStorage()
        self.loadingText = None

        # for not momada only:
        self.levelGeom = None
        self.olc = None
        self.occluders = None

        # for momada only:
        self.momadaAreas = []
        self.momadaAreaName2areaModel = {}

    def getFlagPoint_Point(self, team):
        return self.LevelData[self.levelName]['flagpoint_points'][team]

    def getFlagPoint(self, team):
        return self.LevelData[self.levelName]['flag_points'][team]
    
    def getCapturePoint(self):
        return self.LevelData[self.levelName]['cap_point']

    def setLevel(self, level):
        self.levelName = level

    def getLevel(self):
        return self.levelName

    def getCameraOfCurrentLevel(self):
        return self.LevelData[self.getLevel()]['camera']

    def getSpawnPoints(self):
        # Return the spawn points for this level.
        pointData = self.LevelData[self.levelName]['spawn_points']
        if self.levelName == "momada":
            return pointData
        else:
            if self.mg.gameMode in [GGG.GameModes.CASUAL, GGG.GameModes.KOTH]:
                # These points come from src.coginvasion.distributed.HoodMgr,
                # which is a tuple of a bunch of arrays with pos as first
                # 3, and hpr as last 3 list elements.
                #
                # Disect the arrays and return a tuple holding a Point3 pos, and a Vec3 hpr.
                array = []
                for posAndHpr in pointData:
                    array.append(
                        (
                            Point3(
                                posAndHpr[0],
                                posAndHpr[1],
                                posAndHpr[2]
                            ),

                            Vec3(
                                posAndHpr[3],
                                posAndHpr[4],
                                posAndHpr[5]
                            )
                        )
                    )
            elif self.mg.gameMode == GGG.GameModes.CTF:
                array = pointData[self.mg.team]
            return array

    def getNameOfCurrentLevel(self):
        return self.LevelData[self.getLevel()]['name']

    def load(self):
        self.unload()
        if self.loadingText:
            self.loadingText.destroy()
            self.loadingText = None
        self.loadingText = OnscreenText(text = "",
            font = CIGlobals.getMinnieFont(), fg = (1, 1, 1, 1))
        self.loadingText.setBin('gui-popup', 0)
        base.graphicsEngine.renderFrame()
        base.graphicsEngine.renderFrame()
        if self.levelName == "momada":
            # momada is completely different from the other levels,
            # so it has it's own separate method for loading.
            self.__momadaLoad()
        elif self.levelName in ['cbhq', 'sbf']:
            # Cog hqs are just one model with everything in it. no dna loading needed.
            modelPath = self.LevelData[self.levelName]['model']
            self.levelGeom = loader.loadModel(modelPath)
            self.levelGeom.flattenMedium()
            self.levelGeom.reparentTo(render)
            if self.LevelData[self.levelName].get('occluders'):
                self.occluders = loader.loadModel(self.LevelData[self.levelName]['occluders'])
                for occluderNode in self.occluders.findAllMatches('**/+OccluderNode'):
                    base.render.setOccluder(occluderNode)
                    occluderNode.node().setDoubleSided(True)
            if self.levelName == 'sbf':
                base.camLens.setFar(250)
        else:
            # It's a playground with dna and stuff. Just do the
            # normal loading procedure.
            dnaFiles = self.LevelData[self.levelName]['dna']
            loadDNAFile(self.dnaStore, 'phase_4/dna/storage.pdna')
            for index in range(len(dnaFiles)):
                if index == len(dnaFiles) - 1:
                    node = loadDNAFile(self.dnaStore, dnaFiles[index])
                    if node.getNumParents() == 1:
                        self.levelGeom = NodePath(node.getParent(0))
                        self.levelGeom.reparentTo(hidden)
                    else:
                        self.levelGeom = hidden.attachNewNode(node)
                    if self.levelName == 'ttc' and dnaFiles[index] == 'phase_4/dna/new_ttc_sz.pdna':
                        self.levelGeom.find('**/prop_gazebo_DNARoot').removeNode()
                    else:
                        self.levelGeom.flattenMedium()
                    gsg = base.win.getGsg()
                    if gsg:
                        self.levelGeom.prepareScene(gsg)
                    self.levelGeom.reparentTo(render)
                else:
                    loadDNAFile(self.dnaStore, dnaFiles[index])
            children = self.levelGeom.findAllMatches('**/*doorFrameHole*')
            
            for child in children:
                child.hide()

        self.olc = ZoneUtil.getOutdoorLightingConfig(self.LevelData[self.levelName].get('name'))
        self.olc.setupAndApply()

        if self.loadingText:
            self.loadingText.destroy()
            self.loadingText = None

    def __momadaLoad(self):

        def attachArea(itemNum):
            name = 'MomadaArea-%s' % itemNum
            area = self.momadaAreaName2areaModel.get(name)
            parents = self.LevelData['momada']['parents']
            parent = parents[itemNum]
            if type(parent) == type(""):
                parent = self.momadaAreas[itemNum - 1].find('**/' + parent)
            pos = self.LevelData['momada']['model_positions'][itemNum]
            hpr = self.LevelData['momada']['model_orientations'][itemNum]
            area.reparentTo(parent)
            area.setPos(pos)
            area.setHpr(hpr)

        _numItems = 0
        name = None
        for item in self.LevelData['momada']['models']:
            name = 'MomadaArea-%s' % _numItems
            area = loader.loadModel(item)
            self.momadaAreas.append(area)
            self.momadaAreaName2areaModel[name] = area
            attachArea(_numItems)
            _numItems += 1
            self.notify.info("Loaded and attached %s momada areas." % _numItems)

    def unload(self):
        render.clearOccluder()
        if self.olc:
            self.olc.cleanup()
            self.olc = None
        if self.levelName == "sbf":
            base.camLens.setFar(CIGlobals.DefaultCameraFar)
        if self.levelName == "momada":
            for area in self.momadaAreas:
                self.momadaAreas.remove(area)
                area.removeNode()
                del area
            self.momadaAreas = []
            self.momadaAreaName2areaModel = {}
        else:
            if self.occluders:
                self.occluders.removeNode()
                self.occluders = None
            if self.levelGeom:
                self.levelGeom.removeNode()
                self.levelGeom = None

    def cleanup(self):
        self.momadaAreas = None
        self.momadaAreaName2areaModel = None
        if self.dnaStore:
            self.dnaStore.reset_nodes()
            self.dnaStore.reset_hood_nodes()
            self.dnaStore.reset_place_nodes()
            self.dnaStore.reset_hood()
            self.dnaStore.reset_fonts()
            self.dnaStore.reset_DNA_vis_groups()
            self.dnaStore.reset_materials()
            self.dnaStore.reset_block_numbers()
            self.dnaStore.reset_block_zones()
            self.dnaStore.reset_suit_points()
        self.dnaStore = None
Exemple #14
0
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()