Beispiel #1
0
class GameScreen(object):
    '''
    The GameScreen object is the base of all screens in the game. Panda3d offers no support for screen
    management as everything is done within the context of a 'World' object in 3d space.
    
    Panda3d draws in a tree structure:
    render2d
    -- render
       -- modal node (designed by us to show modal screens on top of everything else)
          -- screen nodes
    '''

    hidden = False
    models = []
    
    
    
    def __init__(self,screen_manager,screen_name):
        '''
        Each GameScreen object takes in a screen manager and the screen's text name
        The Screen's text name is useful for titles or inherited screens like the
        service menu where you would want the inherited screen to display a unique title
        at the top of the screen.
        '''
        self.screen_manager = screen_manager
        self.name = screen_name
        
        """
        In each screen, we have a 2d node and a 3d node. The render tree renders 2d and 3d objects
        using separate branches, so all 2d objects must be parented to a 2d node. All 3d objects
        must be parented to a 3d node.
        
        Here we set up a 2d node for drawing 2d objects on our screen. We also set up a 3d node
        that is responsible for drawing 3d objects. Both are parented to our screen manager
        so the screen manager can easily remove these nodes from the tree in order to hide the screen.
        """
        self.node2d = self.screen_manager.get2dNode().attachNewNode(PandaNode(screen_name + "2d"))
        self.node = self.screen_manager.getNode().attachNewNode(PandaNode(screen_name + "3d"))
        
        self.object = DirectObject()
        
        """
        Each major playfield location can have stacks of objects. Objects that are placed using
        place_model() with pos set to a keyword location (left_ramp, left_loop, center_ramp, side_ramp, right_ramp,
        or right_loop) then the model is placed on top of the stack for that location.
        
        When models are removed, the stacks move down
        """
        
        self.stack = {}
        self.stack['left_ramp'] = []
        self.stack['left_loop'] = []
        self.stack['center_ramp'] = []
        self.stack['side_ramp'] = []
        self.stack['right_ramp'] = []
        self.stack['right_loop'] = []
        
        self.last_removed_model = {}
        self.last_removed_model['left_ramp'] = ""
        self.last_removed_model['left_loop'] = ""
        self.last_removed_model['center_ramp'] = ""
        self.last_removed_model['side_ramp'] = ""
        self.last_removed_model['right_ramp'] = ""
        self.last_removed_model['right_loop'] = ""
        
        self.lowest_position = {}
        self.lowest_position['center_ramp'] = (1.5,120,-11)
        self.lowest_position['side_ramp'] = (20, 120, -30)
        self.lowest_position['right_ramp'] = (33.5, 120, -11)
        self.lowest_position['left_ramp'] =  (-13, 120, -23.5)
        self.lowest_position['right_loop'] = (0,0,0)
        self.lowest_position['left_loop'] = (0,0,0)
        
        
    def destroy(self):
        for model in self.models:
            model['model'].destroy()
            model['light'].destroy()
            
        self.models = []
        
    def hide(self):
        self.hidden = True
        self.node2d.detachNode()
        self.node.detachNode()
        self.object.ignoreAll()
    
    def show(self):
        self.hidden = False
        self.node2d.reparentTo(self.screen_manager.get2dNode())
        self.node.reparentTo(self.screen_manager.getNode())
        
    def is_hidden(self):
        return self.hidden
    
    def place_model(self, model_name, scale, pos, rotate = False, rotspeed = 4, h = 0, p = 0, r = 0, reference = "", mode = ""):
        model = base.loader.loadModel("assets/models/" + model_name)
        
        if reference == "":
            reference = "model" + str(random.random())
        
        if type(pos) is tuple:
            model.setPos(pos)
        elif type(pos) is str:
            self.stack[pos].append(reference)
            lowest_pos = self.lowest_position[pos]
            model.setPos((lowest_pos[0],lowest_pos[1],lowest_pos[2] + (10 * (len(self.stack[pos]) - 1))))
        model.setScale(scale)
        if rotate:
            rotate_int = model.hprInterval( rotspeed, Vec3(360,p,r) ).loop()
            
        model.setHpr((h,p,r))
        
        
        if base.displayFlipped:
            model.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))
        
        model.reparentTo(self.node)
        
        dlight = DirectionalLight('my dlight')
        dlnp = render.attachNewNode(dlight)
        model.setLight(dlnp)
        
        
        
        result = {}
        result['light'] = dlnp
        result['model'] = model
        result['reference'] = reference
        result['name'] = model_name
        result['mode'] = mode
        
        self.models.append(result)
        
        return result
        
    def remove_bottom_model(self, location):
        if len(self.stack[location]) < 1: return
        lower_model_ref = self.stack[location][0]
        mObj = self.get_model(lower_model_ref)
        self.last_removed_model[location] = mObj['name'].split(".")[0]
        self.remove_model(lower_model_ref)
        self.shift_down(location)
        print "last removed models " + str(self.last_removed_model)
        
    def shift_down(self, location):
        if len(self.stack[location]) < 1: return
        lower_model_ref = self.stack[location][0]
        lowest_pos = self.lowest_position[location]
        first_model = True
        for mRef in self.stack[location]:
            model = self.get_model(mRef)
            if first_model:
                model['model'].posInterval(pos=lowest_pos,duration=1.2,blendType='easeIn').start()
                first_model = False
            else:
                m = model['model']
                model['model'].posInterval(pos=(m.getX(), m.getY(), m.getZ() - 10), duration=1.2, blendType='easeIn').start()
    
    def location_has_object(self, location, mode = ""):
        if len(self.stack[location]) == 0:
            return False
        
        if mode != "":
            lower_model_ref = self.stack[location][0]
            mObj = self.get_model(lower_model_ref)
            #print "Location_has_object " + location + " " + mode + " " + mObj['mode']
            if mObj['mode'] == mode:
                return True
            else:
                return False
        
        return len(self.stack[location]) > 0        
                                                     
    def remove_model(self, reference):
        mSize = (0,0,0)
        for model in self.models:
            if model['reference'] == reference:
                mSize = self.model_size(model['model'])
                model['light'].removeNode()
                model['model'].removeNode()
                self.models.remove(model)
                
        
        for location in self.stack:
            if reference in self.stack[location]:
                self.stack[location].remove(reference)
                print "Reference = " + reference
                print location + " Stack " + str(self.stack[location])
                
                for model in self.stack[location]:
                    m = self.get_model(model)
                    mObj = m['model']
                    x = mObj.getX()
                    y = mObj.getY()
                    z = mObj.getZ()
                
    def clear_stacks(self, location = "all"):
        referencesToRemove = []
        for loc_name in self.stack:
            if loc_name == location or location == "all":
                for model in self.stack[loc_name]:
                    referencesToRemove.append(model)
                    
        for model in referencesToRemove:
            self.remove_model(model)
            
    def get_model(self, reference):
        for model in self.models:
            if model['reference'] == reference:
                return model
        return None
    
    def model_size(self, model):
        min, max = model.getTightBounds()
        return max-min
    
    def set_stack_position(self, stack, position):
        lowest_pos = position
        i = 0
        for m in self.stack[stack]:
            mObj = self.get_model(m)
            model = mObj['model']
            model.setPos((lowest_pos[0],lowest_pos[1],lowest_pos[2] + (10 * i)))
            i += 1
            
    def get_last_removed_model(self, location):
        pass
    def clear_last_removed_model(self, location):
        pass
        
        
Beispiel #2
0
class SCTerminal(SCElement):

    def __init__(self, linkedEmote = None):
        SCElement.__init__(self)
        self.setLinkedEmote(linkedEmote)
        scGui = loader.loadModel(SCMenu.GuiModelName)
        self.emotionIcon = scGui.find('**/emotionIcon')
        self.setDisabled(False)
        self.__numCharges = -1
        self._handleWhisperModeSV = StateVar(False)
        self._handleWhisperModeFC = None
        return

    def destroy(self):
        self._handleWhisperModeSV.set(False)
        if self._handleWhisperModeFC:
            self._handleWhisperModeFC.destroy()
        self._handleWhisperModeSV.destroy()
        SCElement.destroy(self)

    def privSetSettingsRef(self, settingsRef):
        SCElement.privSetSettingsRef(self, settingsRef)
        if self._handleWhisperModeFC is None:
            self._handleWhisperModeFC = FunctionCall(self._handleWhisperModeSVChanged, self._handleWhisperModeSV)
            self._handleWhisperModeFC.pushCurrentState()
        self._handleWhisperModeSV.set(self.settingsRef is not None and not self.isWhisperable())
        return

    def _handleWhisperModeSVChanged(self, handleWhisperMode):
        if handleWhisperMode:
            self._wmcListener = DirectObject()
            self._wmcListener.accept(self.getEventName(SCWhisperModeChangeEvent), self._handleWhisperModeChange)
        elif hasattr(self, '_wmcListener'):
            self._wmcListener.ignoreAll()
            del self._wmcListener
            self.invalidate()

    def _handleWhisperModeChange(self, whisperMode):
        self.invalidate()

    def handleSelect(self):
        messenger.send(self.getEventName(SCTerminalSelectedEvent))
        if self.hasLinkedEmote() and self.linkedEmoteEnabled():
            messenger.send(self.getEventName(SCTerminalLinkedEmoteEvent), [self.linkedEmote])

    def isWhisperable(self):
        return True

    def getLinkedEmote(self):
        return self.linkedEmote

    def setLinkedEmote(self, linkedEmote):
        self.linkedEmote = linkedEmote
        self.invalidate()

    def hasLinkedEmote(self):
        return self.linkedEmote is not None

    def linkedEmoteEnabled(self):
        if Emote.globalEmote:
            return Emote.globalEmote.isEnabled(self.linkedEmote)

    def getCharges(self):
        return self.__numCharges

    def setCharges(self, nCharges):
        self.__numCharges = nCharges
        if nCharges is 0:
            self.setDisabled(True)

    def isDisabled(self):
        return self.__disabled or self.isWhispering() and not self.isWhisperable()

    def setDisabled(self, bDisabled):
        self.__disabled = bDisabled

    def onMouseClick(self, event):
        if not self.isDisabled():
            SCElement.onMouseClick(self, event)
            self.handleSelect()

    def getMinDimensions(self):
        width, height = SCElement.getMinDimensions(self)
        if self.hasLinkedEmote():
            width += 1.3
        return (width, height)

    def finalize(self, dbArgs = {}):
        if not self.isDirty():
            return
        args = {}
        if self.hasLinkedEmote():
            self.lastEmoteIconColor = self.getEmoteIconColor()
            self.emotionIcon.setColorScale(*self.lastEmoteIconColor)
            args.update({'image': self.emotionIcon,
             'image_pos': (self.width - 0.6, 0, -self.height * 0.5)})
        if self.isDisabled():
            args.update({'rolloverColor': (0, 0, 0, 0),
             'pressedColor': (0, 0, 0, 0),
             'rolloverSound': None,
             'clickSound': None,
             'text_fg': self.getColorScheme().getTextDisabledColor() + (1,)})
        args.update(dbArgs)
        SCElement.finalize(self, dbArgs=args)
        return

    def getEmoteIconColor(self):
        if self.linkedEmoteEnabled() and not self.isWhispering():
            r, g, b = self.getColorScheme().getEmoteIconColor()
        else:
            r, g, b = self.getColorScheme().getEmoteIconDisabledColor()
        return (r,
         g,
         b,
         1)

    def updateEmoteIcon(self):
        if hasattr(self, 'button'):
            self.lastEmoteIconColor = self.getEmoteIconColor()
            for i in xrange(self.button['numStates']):
                self.button['image%s_image' % i].setColorScale(*self.lastEmoteIconColor)

        else:
            self.invalidate()

    def enterVisible(self):
        SCElement.enterVisible(self)
        if hasattr(self, 'lastEmoteIconColor'):
            if self.getEmoteIconColor() != self.lastEmoteIconColor:
                self.invalidate()

        def handleWhisperModeChange(whisperMode, self = self):
            if self.hasLinkedEmote():
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()

        self.accept(self.getEventName(SCWhisperModeChangeEvent), handleWhisperModeChange)

        def handleEmoteEnableStateChange(self = self):
            if self.hasLinkedEmote():
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()

        if self.hasLinkedEmote():
            if Emote.globalEmote:
                self.accept(Emote.globalEmote.EmoteEnableStateChanged, handleEmoteEnableStateChange)

    def exitVisible(self):
        SCElement.exitVisible(self)
        self.ignore(self.getEventName(SCWhisperModeChangeEvent))
        if Emote.globalEmote:
            self.ignore(Emote.globalEmote.EmoteEnableStateChanged)

    def getDisplayText(self):
        if self.getCharges() is not -1:
            return self.text + ' (%s)' % self.getCharges()
        else:
            return self.text
Beispiel #3
0
class mode_head():
    def __init__(self,base,Golog, folder_path = None, parent = None):
        # Set up basic attributes
        self.base = base
        self.golog = Golog
        self.bools = {'textboxes':True}
        self.buttons = dict()
        self.window_tasks = dict()
        self.bt = None
        self.mw = None
        self.listener = DirectObject()
        self.folder_path = folder_path #absolute path of golog folder '/path/to/golog/folder'
        if self.folder_path:
            self.file_path = os.path.abspath(self.folder_path + '/' + self.golog.label+ '.golog')
            autosave = True
        self.has_window = False
        self.parent = parent #for autosaving up to original golog
        self.reset = self.basic_reset
        self.garbage = [] #list of deleted math_data/graphics_data etc.


        #create a 2d rende
        self.render2d = NodePath('2d render')
        self.camera2D = self.render2d.attachNewNode(Camera('2d Camera'))
        self.camera2D.setDepthTest(False)
        self.camera2D.setDepthWrite(False)
        lens = OrthographicLens()
        lens.setFilmSize(2, 2)
        lens.setNearFar(-1000, 1000)
        self.camera2D.node().setLens(lens)


        # make a dictionary of mode_heads in the underlying golog
        if hasattr(self.golog,'mode_heads'):
            m = 0
            while m in self.golog.mode_heads.keys(): m+=1 #get smallest unused mode_head index
            self.index = m
            self.label = self.golog.label+"_mode_head_"+str(self.index)
            self.golog.mode_heads[self.index] = self

        else:
            self.golog.mode_heads = dict()
            self.index = 0
            self.label = self.golog.label+"_mode_head_"+ str(self.index)
            self.golog.mode_heads[self.index] = self
        ##########

        ### set up collision handling ###
        self.queue = CollisionHandlerQueue()

        ### set up selection tools
        self.create_list = [[],[]] #select nodes and add to create_list in order to create higher simplecies
        self.bools = {'selecter':False,'textboxes':True,'shift_clicked':False} #some bools that will be usefull
        self.dict = {'shift_pt':[None,None]}


        # set up mouse picker
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = self.golog.camera.attachNewNode(self.pickerNode) #attach collision node to camera
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.pickerNode.set_into_collide_mask(0) #so that collision rays don't collide into each other if there are two mode_heads
        self.golog.cTrav.addCollider(self.pickerNP,self.queue) #send collisions to self.queue
        # set up plane for picking
        self.planeNode = self.golog.render.attachNewNode("plane")
        self.planeNode.setTag("mode_head",self.label) # tag to say it belongs to this mode_head
        self.planeNode.setTag("mode_node", 'plane')
        self.planeFromObject = self.planeNode.attachNewNode(CollisionNode("planeColNode"))
        self.planeFromObject.node().addSolid(CollisionPlane(Plane(Vec3(0,-1,0),Point3(0,0,0))))
        self.plane = self.planeFromObject.node().getSolid(0)
        ###
        # set up preview text
        self.textNP = self.render2d.attachNewNode(TextNode('text node'))
        self.textNP.setScale(.2)
        self.textNP.setPos(-1,0,0)
        self.textNP.show()
        #set up dragging info
        self.grabbed_dict = dict()
        self.drag_dict = dict() #a mapping from selected nodes to their original positions for dragging
        self.mouse_down_loc = (0,0,0)
        self.lowest_level = 3

    def open_math_data(self,math_data):
        if math_data.type == 'golog':
            golog_dict = math_data() #calls the mathdata (which is a golog_dict)
            subgolog = golog_dict['golog']
            subfolder_path = golog_dict['folder_path']

            #### just to be safe, but probably not needed
            subgolog_folder_path = os.path.join(self.folder_path,'subgologs')
            if not os.path.exists(subgolog_folder_path): os.mkdir(subgolog_folder_path)
            ####
            folder_path = os.path.join(self.folder_path, *golog_dict['folder_path'])
            print(folder_path)
            controllable_golog = mode_head(self.base, subgolog, folder_path = folder_path, parent = self)
            window_manager.modeHeadToWindow(self.base, controllable_golog)

        if math_data.type == 'file':
            file_name, file_extension = os.path.splitext(math_data()[-1])
            # if file_extension == '.txt':
            #     tk_funcs.edit_txt(os.path.join(self.folder_path,*math_data()))
            # else:
                #prompt user to select a program
            tk_funcs.run_program('',os.path.join(self.folder_path,*math_data()))
        if math_data.type == 'latex':
            file_dict = math_data()
            tex_folder = os.path.join(os.path.abspath(self.folder_path),*file_dict['folder'])
            tex_file = os.path.join(os.path.abspath(self.folder_path),*file_dict['tex'])




            if 'pdf' in file_dict.keys():
                pdf_file = os.path.join(self.folder_path, *file_dict['pdf'])
            elif os.path.exists(tex_file.split('.tex')[0]+'.pdf'): #if there is a pdf in the folder with the same name
                file_dict['pdf'] = file_dict['tex']
                file_dict['pdf'][-1] = file_dict['pdf'][-1].split('.tex')[0]+'.pdf' #change extension to .pdf
                pdf_file = os.path.join(self.folder_path, *file_dict['pdf'])
            else: pdf_file = None


            tk_funcs.pdf_or_tex(pdf_file, tex_file)

        if math_data.type == 'weblink':
            open_new_tab(math_data())

    def update_math_data(self,simplex, math_data_type, **kwargs):


        if autosave == True: self.save()

        if 'label' in kwargs: simplex.label = kwargs['label']
        self.golog.Simplex_to_Graphics[simplex].textNP.node().setText(simplex.label)
        self.golog.text_preview_set(self.bools['textboxes'])

        if simplex.math_data.type != 'None':
            answer = tk_funcs.are_you_sure('are you sure you want to delete the current math_data?')
            if answer == 'yes':
                self.delete_math_data(simplex)
            else: return
        if math_data_type == 'None':
            simplex.math_data = hcat.Math_Data(type = 'None')

        if math_data_type == 'golog':

            #create a path for all subgologs, if it doesn't already exist
            subgolog_folder_path = os.path.join(self.folder_path,'subgologs')
            if not os.path.exists(subgolog_folder_path): os.mkdir(subgolog_folder_path)




            new_golog = golog.golog(self.base, label = kwargs['label']) #create a new golog

            #create a unique folder path list in subgolog_folder_path
            unique_path = tk_funcs.unique_path(subgolog_folder_path,[kwargs['label']])
            new_folder_path = ['subgologs', *unique_path]
            os.mkdir(os.path.join(self.folder_path , *new_folder_path)) #make the directory as above
            #create a new golog save at new_folder_path/label.golog
            new_save_location = os.path.join(self.folder_path, *new_folder_path, kwargs['label']+'.golog')

            #new_save_location should be a subfolder of subgologs
            gexport(new_golog, new_save_location)

            #math data is a dictionary of the physical golog and it's relative save path list
            golog_dict = {'golog':new_golog, 'folder_path':new_folder_path}
            simplex.math_data = hcat.Math_Data(math_data = golog_dict, type = 'golog')

        if math_data_type == 'file':
            if not os.path.exists(os.path.join(self.folder_path,'files')): os.mkdir(os.path.join(self.folder_path,'files'))
            file_folder_path = ['files']

            file_location = tk_funcs.ask_file_location()
            if not file_location: return #if user cancels
            file_name = os.path.split(file_location)[1] #file name with extension
            file_path = tk_funcs.unique_path(os.path.join(self.folder_path),[*file_folder_path, file_name]) #get a unique file path starting from the file_folder
            copyfile(file_location, os.path.join(self.folder_path,*file_path))
            simplex.math_data = hcat.Math_Data(math_data = file_path, type = 'file')
            #? add handler for if user exits text editor
            #? make asynchronous

        if math_data_type == 'latex':
            #ensure latex folder exists
            if not os.path.exists(os.path.join(self.folder_path,'latex')): os.mkdir(os.path.join(self.folder_path,'latex'))

            # create a uniquely named folder in self.folder_path/latex/ based on simplex.label
            tex_folder_path = tk_funcs.unique_path(root = self.folder_path, path = ['latex',simplex.label])
            os.mkdir(os.path.join(self.folder_path, *tex_folder_path))
            #create a tex file in tex folder
            tex_file_path = [*tex_folder_path, simplex.label+'.tex']
            # ask if want new or to load one
            location = tk_funcs.load_tex(self.folder_path)
            # if new, returns True and copies template tex file
            # if load, returns a path and copies the path into tex_file_path
            true_path = os.path.join(self.folder_path,*tex_file_path)
            if location == True: copyfile(os.path.abspath('./misc_data/config_files/template.tex'), true_path)

                #
                # open(   true_path  , 'w').close()
            if isinstance(location, str): copyfile(location, true_path)

            # make a file dictionary with just tex file in it
            file_dict = {'tex':tex_file_path, 'folder':tex_folder_path}
            simplex.math_data = hcat.Math_Data(math_data = file_dict, type = 'latex')

        if math_data_type == 'weblink':
            weblink = tk_funcs.ask_weblink()
            simplex.math_data = hcat.Math_Data(math_data = weblink, type = 'weblink')


        if math_data_type == 'text':
            #create a text file
            if not os.path.exists(os.path.join(self.folder_path,'files')): os.mkdir(os.path.join(self.folder_path,'files'))
            file_folder_path = ['files']
            file_path = tk_funcs.unique_path(root = self.folder_path, path = ['files',simplex.label+'.txt' ])
            with open(os.path.join(self.folder_path, *file_path),'w') as file: pass
            #create a math_data for it
            simplex.math_data = hcat.Math_Data(math_data = file_path, type = 'file')



        #save golog
        if autosave == True: self.save()
        return simplex.math_data

    def delete_math_data(self,simplex,**kwargs):
        simplex.math_data.delete(self.folder_path)
        simplex.math_data = hcat.Math_Data(type = 'None')

    def setup_window(self, windict):
        self.windict = windict
        for button in self.buttons.keys():
            self.listener.accept(self.windict['bt'].prefix+button, self.buttons[button], extraArgs = [self.windict['mw']])
        for window_task in self.window_tasks.keys():
            base.taskMgr.add(self.window_tasks[window_task], window_task, extraArgs = [self.windict['mw']], appendTask = True)
        self.has_window = True

    def save(self, *ask):
        #if has a parent, save the parent. If not, save itself
        if not self.parent: #if doesn't have a parent mode_head, save parent
        #if we pass something to ask it will ask (this includes mousewatchers)
            if ask:
                save_location = tk_funcs.ask_file_location(initial_dir = self.folder_path)
                if not save_location: return #if user cancels
                print('saving to:\n'+save_location)
                gexport(self.golog,  self.folder_path)
            else:
                gexport(self.golog,  self.file_path)

        else: self.parent.save()# if parent has no mode_head, save itself

    #basic reset function which shuts off the listener and removes button bindings. Should only be called if no "reset" function exists
    def basic_reset(self,*args):
        self.buttons = dict()
        self.window_tasks = dict()
        self.listener.ignoreAll()

    #function to call before deleting the mode_head, for example when closing a window
    def clean(self):
        self.reset()

        #close window
        if self.has_window == True:
            if hasattr(mode_head,'windict'):
                if 'win' in mode_head.windict.keys():
                    self.base.closeWindow(mode_head.windict['win'], keepCamera = True, removeWindow = True)
            self.has_window = False
        del self.golog.mode_heads[self.index]
        del self.reset

    # function to return the only the relevant collision data from the mouseRay
    def get_relevant_entries(self, mw):
        # get list of entries by distance
        if not mw.node().hasMouse(): return
        mpos = mw.node().getMouse()
        self.pickerRay.setFromLens(self.golog.camNode,mpos.getX(),mpos.getY()) #mouse ray goes from camera through the 'lens plane' at position of mouse
        self.golog.cTrav.traverse(self.golog.render)
        self.queue.sortEntries()

        # get the first relevant node traversed by mouseRay
        #### ignore everything with a mode_head tag that is not defined by this mode_head
        for e in self.queue.getEntries():
            if e.getIntoNodePath().getParent().hasTag("mode_head"):
                if e.getIntoNodePath().getParent().getTag("mode_head") == self.label:
                    return (e.getIntoNodePath().getParent(), e.getIntoNodePath().getParent().getTag("mode_node"),e.getSurfacePoint(e.getIntoNodePath()))
                    break
            else:
                entry = e
                break

        #return node create_list in the golog
        entryNP = entry.getIntoNodePath().getParent()
        if entryNP.hasTag('level'): return (entryNP, entryNP.getTag('level'),entryNP.getPos())

    #function to 'pick up' a node by adding it to the dragged dictionary
    def pickup(self, mw):
        if not mw.node().hasMouse(): return
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
        


        ### get position on plane for mouseloc
        mpos = mw.node().getMouse()
        self.pickerRay.setFromLens(self.golog.camNode,mpos.getX(),mpos.getY()) #mouse ray goes from camera through the 'lens plane' at position of mouse
        self.golog.cTrav.traverse(self.golog.render)
        self.queue.sortEntries()
        for e in self.queue.getEntries():
            if e.getIntoNodePath().getParent().hasTag("mode_head"):
                if e.getIntoNodePath().getParent().getTag("mode_head") == self.label:
                    if e.getIntoNodePath().getParent().getTag("mode_node") == 'plane':
                        self.mouse_down_loc = e.getSurfacePoint(e.getIntoNodePath())
                        break


        #if selected node is in the drag_dict, use it to set a mouse location
        # if entryNP in self.drag_dict.keys(): self.mouse_down_loc = entryNP.getPos()

        if node_type in ['0','1']:
            self.grabbed_dict = {'graphics': self.golog.NP_to_Graphics[entryNP],'dragged':False,
                                                        'orig_pos': entryNP.getPos()}
        if node_type == 'plane':
            for node in self.create_list[0]: node.setColorScale(1,1,1,1) #turn white
            self.create_list = [[],[]]
            for node in self.drag_dict.keys():  node.setColorScale(1,1,1,1)
            self.drag_dict = dict() #drag dict is used for multi-dragging
            a = self.plane.distToPlane(self.golog.camera.getPos())/(2*self.golog.camera.node().getLens().getNear())# ratio for camera movement
            self.grabbed_dict = {'graphics':self.golog.camera, 'dragged':False, 'orig_pos': self.golog.camera.getPos(), 'mpos': LPoint3f(a*mpos.getX(),a*0,a*mpos.getY())}
            self.lowest_level = 3



    #function to 'put down' a node, returns true if it's dragged something
    def putdown(self, mw):
        for node in self.drag_dict.keys():
            self.drag_dict[node] = self.golog.NP_to_Graphics[node].graphics_kwargs['pos']
            self.lowest_level = min(self.lowest_level, int(node.getTag('level')))

        if 'dragged' in self.grabbed_dict.keys():
            if self.grabbed_dict['dragged'] == True:
                self.grabbed_dict = dict()
                return True
            self.grabbed_dict = dict()
    #function to select a node and add a 1-simplex between 2 create_list 0-simplecies
    def select_for_creation(self, mw):
        if not mw.node().hasMouse(): return
        #remove selected
        for node in self.drag_dict.keys():
            node.setColorScale(1,1,1,1)
        self.drag_dict = dict()
        self.lowest_level = 3

        ### selection ###
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
        if node_type == '0':# and set(create_list[1:]) = {[]}:
            if entryNP not in self.create_list[0]:
                #?  don't just append, re-sort
                self.create_list[0].append(entryNP)
            entryNP.setColorScale(1,0,0,0) #turn red

        if len(self.create_list[0]) == 2:
            # NP -> graphics -> simplex
            faces = tuple([self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[faceNP]] for faceNP in self.create_list[0][-1:-3:-1]])
            asked_list = tk_funcs.ask_math_data('1-Simplex')
            if not asked_list: #[label, math_data_type,]
                return
            simplex = self.golog.add(faces, label = asked_list[0]) #reversed create_list objects and creates a 1 - simplex from them
            self.update_math_data(simplex, asked_list[1], label = asked_list[0])
            for node in self.create_list[0]: node.setColorScale(1,1,1,1)
            self.create_list[0] = [] #reset create_list

    #open math_data of simplex under mouse
    def mouse_open(self, mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)

        # if spaced on a 0 simplex, open it's math data, or create it
        if node_type in ['0','1']:
            simplex = self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
            if not simplex.math_data():
                asked_list = tk_funcs.ask_math_data(simplex.label)
                if not asked_list:
                    return
                self.update_math_data(simplex, math_data_type = asked_list[1], label = asked_list[0])
                #? make asynchronous


            else:
                self.open_math_data(simplex.math_data)

    # delete a simplex, prompt to delete the math_data and (recursively) it's supported simplecies
    def delete(self,mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
        if node_type in ['0','1']:
            #warning about deleting supported simplecies

            graphics = self.golog.NP_to_Graphics[entryNP]
            simplex = self.golog.Graphics_to_Simplex[graphics]

            if simplex.supports:
                answer = tk_funcs.are_you_sure(simplex.label+' still supports other simplecies. Are you sure you wish to delete?')
                if answer != 'yes': return

            self.delete_math_data(simplex)
            graphics._remove()

    #update the math_data of a simplex under the mouse
    def mouse_update(self, mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
        if node_type in ['0','1']:
            simplex = self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
            asked_list = tk_funcs.ask_math_data(simplex.label)
            if not asked_list:
                return
            self.update_math_data(simplex, math_data_type = asked_list[1], label = asked_list[0])

    #create a simplex given a mouse position
    def create(self, mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)

        if node_type == 'plane':
            for node in self.create_list[0]: node.setColorScale(1,1,1,1) #turn white
            self.create_list = [[],[]]
            asked_list = tk_funcs.ask_math_data('0-Simplex')
            if not asked_list:
                return #if canceled, do not create a simplex
            simplex = self.golog.add(0, pos = entry_pos, label = asked_list[0]) #create a simplex
            self.update_math_data(simplex, math_data_type = asked_list[1], label = asked_list[0])

    #function which checks the drag dictionary and drags stuff
    def drag(self, mw):
        if self.grabbed_dict:
            #get mouse_loc
            mpos = mw.node().getMouse()
            self.pickerRay.setFromLens(self.golog.camNode,mpos.getX(),mpos.getY()) #mouse ray goes from camera through the 'lens plane' at position of mouse
            self.golog.cTrav.traverse(self.golog.render)
            self.queue.sortEntries()
            mouseloc = None
            for e in self.queue.getEntries():
                if e.getIntoNodePath().getParent().getTag("mode_node") == 'plane':
                    mouseloc = e.getSurfacePoint(e.getIntoNodePath())
                    break
            if not mouseloc:return

            self.bools['dragging'] = True


             # if there is something in the drag dict (for multiselect)
            if self.drag_dict:
                self.grabbed_dict['dragged'] = True
                #only drag lowest dim simplecies
                for node in self.drag_dict.keys():
                    if int(node.getTag('level')) == self.lowest_level: self.golog.NP_to_Graphics[node].update({'pos':self.drag_dict[node]+mouseloc-self.mouse_down_loc})

            #if nothing is selected, drag the thing below you
            elif self.grabbed_dict['graphics'] != self.golog.camera: 
                #radius of drag selection
                offset = mouseloc - self.grabbed_dict['graphics'].parent_pos_convolution()
                delta = offset - self.grabbed_dict['orig_pos']
                norm = delta.getX()**2 +delta.getY()**2 +delta.getZ()**2
                if self.grabbed_dict['dragged'] == True or norm > 1:
                    self.grabbed_dict['dragged'] = True
                #if offset magnitude is greater than 1 or dragged == true, actually drag it
                if self.grabbed_dict['dragged'] == True: self.grabbed_dict['graphics'].update({'pos':offset})

            elif self.grabbed_dict['graphics'] == self.golog.camera: 
                        a = self.plane.distToPlane(self.golog.camera.getPos())/(2*self.golog.camera.node().getLens().getNear())# ratio for camera movement
                        self.golog.camera.setPos(self.grabbed_dict['orig_pos']-(a*mpos.getX(),0,a*mpos.getY())+self.grabbed_dict['mpos'])

           
                    


    #send preview of math_data of simplex under the mouse
    def preview(self, mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)

        if node_type == '0':
            simplex =  self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
        elif node_type == '1':
            #? again consider what needs to be shown with 1-simplecies
            simplex =  self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
        else: return

        # set preview up
        if self.has_window:
            if simplex.math_data.type == 'golog':
                self.windict['preview_dr'].setCamera(simplex.math_data()['golog'].camera)
            else:
                self.windict['preview_dr'].setCamera(self.camera2D)
                self.textNP.node().setText("label:\n" +simplex.label+"\n\n math data type:\n" + simplex.math_data.type)
        return

    def pprint(self,mw):
        (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)

        if node_type == '0':
            simplex =  self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
        elif node_type == '1':
            #? again consider what needs to be shown with 1-simplecies
            simplex =  self.golog.Graphics_to_Simplex[self.golog.NP_to_Graphics[entryNP]]
        else: return

        simplex.pprint()


    #tool for selecting multiple simplecies
    def multi_select(self,mw):
        if isinstance(mw, NodePath):
            entryNP = mw
            node_type = entryNP.getTag('level')
        else: return

        #reset select and create
        if node_type in ['0','1']:
            if entryNP in self.drag_dict.keys():
                del self.drag_dict[entryNP]
                entryNP.setColorScale(1,1,1,1)
                self.lowest_level = min([*[int(node.getTag('level')) for node in self.drag_dict.keys()],3])
            else:
                self.drag_dict[entryNP] = self.golog.NP_to_Graphics[entryNP].graphics_kwargs['pos']
                entryNP.setColorScale(.5,.5,0,1)
                self.lowest_level = min(self.lowest_level, int(entryNP.getTag('level')))

    def shift_box(self,mw):
        if None in self.dict['shift_pt']:
            self.dict['shift_pt'] = [None,None]
            return
        pt_1 = self.dict['shift_pt'][0]
        pt_2 = self.dict['shift_pt'][1]
        if sum([x**2 for x in list(pt_2-pt_1)]) <= 1:
            (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
            self.multi_select(entryNP)

        #create vectors
        N = self.planeFromObject.node().getSolid(0).getNormal()
        x = [(pt_2-pt_1).getX(),0,0]
        z = [0,0,(pt_2-pt_1).getZ()]
        campos = list(self.golog.camera.getPos())
        for simplex in self.golog.sSet.rawSimps:
            node = self.golog.Simplex_to_Graphics[simplex].node_list[0]
            P = list(node.getPos())
            if t_int(pt_1,x,z,campos,P): self.multi_select(node)

        self.dict['shift_pt'] = [None,None]


    def consolidate(self, selected = False,sel_simp = None):
        #ask for label, or cancel
        G = self.golog

        # if a collection isn't provided, use the (multi-select) drag dictionary
        if not selected: selected = [G.Graphics_to_Simplex[G.NP_to_Graphics[node]] for node in self.drag_dict.keys()]
        if not selected: return #return if there was nothing passed, and nothing in the drag_dict
        for simp in selected:
            for face in simp.faces:
                if face not in selected: selected.append(face)

        # #? select a simplex to consolidate into
        # sel_simp = None
        # for simp in selected: 
        #     if simp.math_data.type == 'None':
                
        #         sel_simp = simp

        #make a golog from selected
        new_golog = golog.golog(self.base, label ='test')

        def add(simplex):
            for face in simplex.faces:
                add(face)
            new_golog.add(simplex, pos = G.Simplex_to_Graphics[simplex].graphics_kwargs['pos'])
        for simplex in selected: add(simplex)
        

        #consolidate into 1 simplex
        if sel_simp:
            #consolidate into sel_simp
            subgolog_folder_path = os.path.join(self.folder_path,'subgologs')
            unique_path = tk_funcs.unique_path(subgolog_folder_path,[sel_simp.label])
            new_folder_path = ['subgologs', *unique_path]
            
            sel_simp.math_data = hcat.Math_Data(math_data = {'golog':new_golog, 'folder_path':new_folder_path}, type = 'golog')
            
            #? remove simplexes and place at selected simplex location
            return sel_simp


        #create an entirely new simplex to put the golog into
        else:
            #? ask for label / cancel
            label = "test"
            subgolog_folder_path = os.path.join(self.folder_path,'subgologs')
            unique_path = tk_funcs.unique_path(subgolog_folder_path,[label])
            new_folder_path = ['subgologs', *unique_path]

            #create a simplex with average position of things in golog
            avg = LPoint3f(*[sum([G.Simplex_to_Graphics[simplex].graphics_kwargs['pos'][i]/len(new_golog.sSet.simplecies[()])  for simplex in new_golog.sSet.simplecies[()]]) for i in range(3)])
            s = self.golog.add(0, label ='test', math_data = hcat.Math_Data(math_data = {'golog':new_golog, 'folder_path':new_folder_path}, type = 'golog'), pos = LPoint3f(avg))

            return s

    ########## BEGIN DEFINING MODES ##########

    #selection and creation mode
    def selection_and_creation(self, windict):
        def mouse1(mw):
            if not mw: return
            self.bools['shift_clicked'] = False
            self.pickup(mw)

        def shift_mouse1(mw):
            if not mw: return
            #on click, begin a rectagle dragging function
            (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
            self.dict['shift_pt'][0] = entry_pos
            self.bools['shift_clicked'] = True

        def mouse1_up(mw):
            dropped_bool = self.putdown(mw)
            if self.bools['shift_clicked']:
                (entryNP, node_type, entry_pos) = self.get_relevant_entries(mw)
                self.dict['shift_pt'][1] = entry_pos
                self.shift_box(mw)
                self.bools['shift_clicked'] = False

            elif dropped_bool:
                pass
            else:
                self.select_for_creation(mw)




        def space(mw):
            if not mw: return
            self.mouse_open(mw)

        def u(mw):
            if not mw: return
            #?  do a change not just update
            self.mouse_update(mw)

        def c(mw):
            self.consolidate()
        def p(mw):
            if not mw: return
            self.pprint(mw)

        def mouse3(mw):
            if not mw: return
            self.create(mw)

        def backspace(mw):
            if not mw: return
            self.delete(mw)

        def mouse_watch_task(mw,task):
            if not mw: return task.cont
            if not mw.node().hasMouse(): return task.cont
            self.drag(mw)
            self.preview(mw)
            return task.cont

        def wheel_up(mw):
            if not mw:return
            a = self.plane.distToPlane(self.golog.camera.getPos())/(2*self.golog.camera.node().getLens().getNear())# ratio for camera movement
            self.golog.camera.setPos( self.golog.camera.getPos() + (0,10,0) ) #fix for offset by storing a global camera - plane ratio

        def wheel_down(mw):
            if not mw:return
            self.golog.camera.setPos( self.golog.camera.getPos() - (0,10,0) )

        def reset(*args):
            self.buttons = dict()
            self.window_task = dict()
            self.reset = self.basic_reset
        self.reset = reset

        self.buttons = {'mouse1':mouse1, 'mouse1-up':mouse1_up, 'mouse3':mouse3,'c':c,
        'space':space, 'escape':self.reset, 's':self.save, 'u':u,'backspace':backspace,
        'shift-mouse1':shift_mouse1,'p':p,'wheel_up':wheel_up, "wheel_down":wheel_down}
        self.window_tasks = {'mouse_watch_task':mouse_watch_task}
        self.setup_window(windict)
class DirectEntry(DirectFrame):
    """
    DirectEntry(parent) - Create a DirectGuiWidget which responds
    to keyboard buttons
    """

    directWtext = ConfigVariableBool('direct-wtext', 1)

    AllowCapNamePrefixes = ("Al", "Ap", "Ben", "De", "Del", "Della", "Delle", "Der", "Di", "Du",
                            "El", "Fitz", "La", "Las", "Le", "Les", "Lo", "Los",
                            "Mac", "St", "Te", "Ten", "Van", "Von", )
    ForceCapNamePrefixes = ("D'", "DeLa", "Dell'", "L'", "M'", "Mc", "O'", )

    def __init__(self, parent = None, **kw):
        # Inherits from DirectFrame
        # A Direct Frame can have:
        # - A background texture (pass in path to image, or Texture Card)
        # - A midground geometry item (pass in geometry)
        # - A foreground text Node (pass in text string or Onscreen Text)
        # For a direct entry:
        # Each button has 3 states (focus, noFocus, disabled)
        # The same image/geom/text can be used for all three states or each
        # state can have a different text/geom/image
        # State transitions happen automatically based upon mouse interaction
        optiondefs = (
            # Define type of DirectGuiWidget
            ('pgFunc',          PGEntry,          None),
            ('numStates',       3,                None),
            ('state',           DGG.NORMAL,       None),
            ('entryFont',       None,             DGG.INITOPT),
            ('width',           10,               self.setup),
            ('numLines',        1,                self.setup),
            ('focus',           0,                self.setFocus),
            ('cursorKeys',      1,                self.setCursorKeysActive),
            ('obscured',        0,                self.setObscureMode),
            # Setting backgroundFocus allows the entry box to get keyboard
            # events that are not handled by other things (i.e. events that
            # fall through to the background):
            ('backgroundFocus', 0,                self.setBackgroundFocus),
            # Text used for the PGEntry text node
            # NOTE: This overrides the DirectFrame text option
            ('initialText',     '',               DGG.INITOPT),
            # Command to be called on hitting Enter
            ('command',        None,              None),
            ('extraArgs',      [],                None),
            # Command to be called when enter is hit but we fail to submit
            ('failedCommand',  None,              None),
            ('failedExtraArgs',[],                None),
            # commands to be called when focus is gained or lost
            ('focusInCommand', None,              None),
            ('focusInExtraArgs', [],              None),
            ('focusOutCommand', None,             None),
            ('focusOutExtraArgs', [],             None),
            # Sounds to be used for button events
            ('rolloverSound',   DGG.getDefaultRolloverSound(), self.setRolloverSound),
            ('clickSound',      DGG.getDefaultClickSound(),    self.setClickSound),
            ('autoCapitalize',  0,                self.autoCapitalizeFunc),
            ('autoCapitalizeAllowPrefixes', DirectEntry.AllowCapNamePrefixes, None),
            ('autoCapitalizeForcePrefixes', DirectEntry.ForceCapNamePrefixes, None),
            )
        # Merge keyword options with default options
        self.defineoptions(kw, optiondefs)

        # Initialize superclasses
        DirectFrame.__init__(self, parent)

        if self['entryFont'] == None:
            font = DGG.getDefaultFont()
        else:
            font = self['entryFont']

        # Create Text Node Component
        self.onscreenText = self.createcomponent(
            'text', (), None,
            OnscreenText,
            (), parent = hidden,
            # Pass in empty text to avoid extra work, since its really
            # The PGEntry which will use the TextNode to generate geometry
            text = '',
            align = TextNode.ALeft,
            font = font,
            scale = 1,
            # Don't get rid of the text node
            mayChange = 1)

        # We can get rid of the node path since we're just using the
        # onscreenText as an easy way to access a text node as a
        # component
        self.onscreenText.removeNode()

        # Bind command function
        self.bind(DGG.ACCEPT, self.commandFunc)
        self.bind(DGG.ACCEPTFAILED, self.failedCommandFunc)

        self.accept(self.guiItem.getFocusInEvent(), self.focusInCommandFunc)
        self.accept(self.guiItem.getFocusOutEvent(), self.focusOutCommandFunc)

        # listen for auto-capitalize events on a separate object to prevent
        # clashing with other parts of the system
        self._autoCapListener = DirectObject()

        # Call option initialization functions
        self.initialiseoptions(DirectEntry)

        if not hasattr(self, 'autoCapitalizeAllowPrefixes'):
            self.autoCapitalizeAllowPrefixes = DirectEntry.AllowCapNamePrefixes
        if not hasattr(self, 'autoCapitalizeForcePrefixes'):
            self.autoCapitalizeForcePrefixes = DirectEntry.ForceCapNamePrefixes

        # Update TextNodes for each state
        for i in range(self['numStates']):
            self.guiItem.setTextDef(i, self.onscreenText.textNode)

        # Now we should call setup() again to make sure it has the
        # right font def.
        self.setup()

        # Update initial text
        self.unicodeText = 0
        if self['initialText']:
            self.enterText(self['initialText'])

    def destroy(self):
        self.ignoreAll()
        self._autoCapListener.ignoreAll()
        DirectFrame.destroy(self)

    def setup(self):
        self.guiItem.setupMinimal(self['width'], self['numLines'])

    def setFocus(self):
        PGEntry.setFocus(self.guiItem, self['focus'])

    def setCursorKeysActive(self):
        PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])

    def setObscureMode(self):
        PGEntry.setObscureMode(self.guiItem, self['obscured'])

    def setBackgroundFocus(self):
        PGEntry.setBackgroundFocus(self.guiItem, self['backgroundFocus'])

    def setRolloverSound(self):
        rolloverSound = self['rolloverSound']
        if rolloverSound:
            self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound)
        else:
            self.guiItem.clearSound(DGG.ENTER + self.guiId)

    def setClickSound(self):
        clickSound = self['clickSound']
        if clickSound:
            self.guiItem.setSound(DGG.ACCEPT + self.guiId, clickSound)
        else:
            self.guiItem.clearSound(DGG.ACCEPT + self.guiId)

    def commandFunc(self, event):
        if self['command']:
            # Pass any extra args to command
            apply(self['command'], [self.get()] + self['extraArgs'])

    def failedCommandFunc(self, event):
        if self['failedCommand']:
            # Pass any extra args
            apply(self['failedCommand'], [self.get()] + self['failedExtraArgs'])

    def autoCapitalizeFunc(self):
        if self['autoCapitalize']:
            self._autoCapListener.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self._autoCapListener.accept(self.guiItem.getEraseEvent(), self._handleErasing)
        else:
            self._autoCapListener.ignore(self.guiItem.getTypeEvent())
            self._autoCapListener.ignore(self.guiItem.getEraseEvent())

    def focusInCommandFunc(self):
        if self['focusInCommand']:
            apply(self['focusInCommand'], self['focusInExtraArgs'])
        if self['autoCapitalize']:
            self.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self.accept(self.guiItem.getEraseEvent(), self._handleErasing)

    def _handleTyping(self, guiEvent):
        self._autoCapitalize()
    def _handleErasing(self, guiEvent):
        self._autoCapitalize()

    def _autoCapitalize(self):
        name = self.get().decode('utf-8')
        # capitalize each word, allowing for things like McMutton
        capName = ''
        # track each individual word to detect prefixes like Mc
        wordSoFar = ''
        # track whether the previous character was part of a word or not
        wasNonWordChar = True
        for i in xrange(len(name)):
            character = name[i]
            # test to see if we are between words
            # - Count characters that can't be capitalized as a break between words
            #   This assumes that string.lower and string.upper will return different
            #   values for all unicode letters.
            # - Don't count apostrophes as a break between words
            if ((string.lower(character) == string.upper(character)) and (character != "'")):
                # we are between words
                wordSoFar = ''
                wasNonWordChar = True
            else:
                capitalize = False
                if wasNonWordChar:
                    # first letter of a word, capitalize it unconditionally;
                    capitalize = True
                elif (character == string.upper(character) and
                      len(self.autoCapitalizeAllowPrefixes) and
                      wordSoFar in self.autoCapitalizeAllowPrefixes):
                    # first letter after one of the prefixes, allow it to be capitalized
                    capitalize = True
                elif (len(self.autoCapitalizeForcePrefixes) and
                      wordSoFar in self.autoCapitalizeForcePrefixes):
                    # first letter after one of the force prefixes, force it to be capitalized
                    capitalize = True
                if capitalize:
                    # allow this letter to remain capitalized
                    character = string.upper(character)
                else:
                    character = string.lower(character)
                wordSoFar += character
                wasNonWordChar = False
            capName += character
        self.enterText(capName.encode('utf-8'))

    def focusOutCommandFunc(self):
        if self['focusOutCommand']:
            apply(self['focusOutCommand'], self['focusOutExtraArgs'])
        if self['autoCapitalize']:
            self.ignore(self.guiItem.getTypeEvent())
            self.ignore(self.guiItem.getEraseEvent())

    def set(self, text):
        """ Changes the text currently showing in the typable region;
        does not change the current cursor position.  Also see
        enterText(). """
        
        self.unicodeText = isinstance(text, types.UnicodeType)
        if self.unicodeText:
            self.guiItem.setWtext(text)
        else:
            self.guiItem.setText(text)

    def get(self, plain = False):
        """ Returns the text currently showing in the typable region.
        If plain is True, the returned text will not include any
        formatting characters like nested color-change codes. """

        wantWide = self.unicodeText or self.guiItem.isWtext()
        if not self.directWtext.getValue():
            # If the user has configured wide-text off, then always
            # return an 8-bit string.  This will be encoded if
            # necessary, according to Panda's default encoding.
            wantWide = False

        if plain:
            if wantWide:
                return self.guiItem.getPlainWtext()
            else:
                return self.guiItem.getPlainText()
        else:
            if wantWide:
                return self.guiItem.getWtext()
            else:
                return self.guiItem.getText()

    def setCursorPosition(self, pos):
        if (pos < 0):
            self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
        else:
            self.guiItem.setCursorPosition(pos)

    def enterText(self, text):
        """ sets the entry's text, and moves the cursor to the end """
        self.set(text)
        self.setCursorPosition(self.guiItem.getNumCharacters())

    def getFont(self):
        return self.onscreenText.getFont()

    def getBounds(self, state = 0):
        # Compute the width and height for the entry itself, ignoring
        # geometry etc.
        tn = self.onscreenText.textNode
        mat = tn.getTransform()
        align = tn.getAlign()
        lineHeight = tn.getLineHeight()
        numLines = self['numLines']
        width = self['width']

        if align == TextNode.ALeft:
            left = 0.0
            right = width
        elif align == TextNode.ACenter:
            left = -width / 2.0
            right = width / 2.0
        elif align == TextNode.ARight:
            left = -width
            right = 0.0

        bottom = -0.3 * lineHeight - (lineHeight * (numLines - 1))
        top = lineHeight

        self.ll.set(left, 0.0, bottom)
        self.ur.set(right, 0.0, top)
        self.ll = mat.xformPoint(Point3.rfu(left, 0.0, bottom))
        self.ur = mat.xformPoint(Point3.rfu(right, 0.0, top))

        vec_right = Vec3.right()
        vec_up = Vec3.up()
        left = (vec_right[0] * self.ll[0]
              + vec_right[1] * self.ll[1]
              + vec_right[2] * self.ll[2])
        right = (vec_right[0] * self.ur[0]
               + vec_right[1] * self.ur[1]
               + vec_right[2] * self.ur[2])
        bottom = (vec_up[0] * self.ll[0]
                + vec_up[1] * self.ll[1]
                + vec_up[2] * self.ll[2])
        top = (vec_up[0] * self.ur[0]
             + vec_up[1] * self.ur[1]
             + vec_up[2] * self.ur[2])
        self.ll = Point3(left, 0.0, bottom)
        self.ur = Point3(right, 0.0, top)

        # Scale bounds to give a pad around graphics.  We also want to
        # scale around the border width.
        pad = self['pad']
        borderWidth = self['borderWidth']
        self.bounds = [self.ll[0] - pad[0] - borderWidth[0],
                       self.ur[0] + pad[0] + borderWidth[0],
                       self.ll[2] - pad[1] - borderWidth[1],
                       self.ur[2] + pad[1] + borderWidth[1]]
        return self.bounds
class SCTerminal(SCElement):

    def __init__(self, linkedEmote = None):
        SCElement.__init__(self)
        self.setLinkedEmote(linkedEmote)
        scGui = loader.loadModel(SCMenu.GuiModelName)
        self.emotionIcon = scGui.find('**/emotionIcon')
        self.setDisabled(False)
        self.__numCharges = -1
        self._handleWhisperModeSV = StateVar(False)
        self._handleWhisperModeFC = None
        return

    def destroy(self):
        self._handleWhisperModeSV.set(False)
        if self._handleWhisperModeFC:
            self._handleWhisperModeFC.destroy()
        self._handleWhisperModeSV.destroy()
        SCElement.destroy(self)

    def privSetSettingsRef(self, settingsRef):
        SCElement.privSetSettingsRef(self, settingsRef)
        if self._handleWhisperModeFC is None:
            self._handleWhisperModeFC = FunctionCall(self._handleWhisperModeSVChanged, self._handleWhisperModeSV)
            self._handleWhisperModeFC.pushCurrentState()
        self._handleWhisperModeSV.set(self.settingsRef is not None and not self.isWhisperable())
        return

    def _handleWhisperModeSVChanged(self, handleWhisperMode):
        if handleWhisperMode:
            self._wmcListener = DirectObject()
            self._wmcListener.accept(self.getEventName(SCWhisperModeChangeEvent), self._handleWhisperModeChange)
        elif hasattr(self, '_wmcListener'):
            self._wmcListener.ignoreAll()
            del self._wmcListener
            self.invalidate()

    def _handleWhisperModeChange(self, whisperMode):
        self.invalidate()

    def handleSelect(self):
        messenger.send(self.getEventName(SCTerminalSelectedEvent))
        if self.hasLinkedEmote() and self.linkedEmoteEnabled():
            messenger.send(self.getEventName(SCTerminalLinkedEmoteEvent), [self.linkedEmote])

    def isWhisperable(self):
        return True

    def getLinkedEmote(self):
        return self.linkedEmote

    def setLinkedEmote(self, linkedEmote):
        self.linkedEmote = linkedEmote
        self.invalidate()

    def hasLinkedEmote(self):
        return self.linkedEmote is not None

    def linkedEmoteEnabled(self):
        if Emote.globalEmote:
            return Emote.globalEmote.isEnabled(self.linkedEmote)

    def getCharges(self):
        return self.__numCharges

    def setCharges(self, nCharges):
        self.__numCharges = nCharges
        if nCharges is 0:
            self.setDisabled(True)

    def isDisabled(self):
        return self.__disabled or self.isWhispering() and not self.isWhisperable()

    def setDisabled(self, bDisabled):
        self.__disabled = bDisabled

    def onMouseClick(self, event):
        if not self.isDisabled():
            SCElement.onMouseClick(self, event)
            self.handleSelect()

    def getMinDimensions(self):
        width, height = SCElement.getMinDimensions(self)
        if self.hasLinkedEmote():
            width += 1.3
        return (width, height)

    def finalize(self, dbArgs = {}):
        if not self.isDirty():
            return
        args = {}
        if self.hasLinkedEmote():
            self.lastEmoteIconColor = self.getEmoteIconColor()
            self.emotionIcon.setColorScale(*self.lastEmoteIconColor)
            args.update({'image': self.emotionIcon,
             'image_pos': (self.width - 0.6, 0, -self.height * 0.5)})
        if self.isDisabled():
            args.update({'rolloverColor': (0, 0, 0, 0),
             'pressedColor': (0, 0, 0, 0),
             'rolloverSound': None,
             'clickSound': None,
             'text_fg': self.getColorScheme().getTextDisabledColor() + (1,)})
        args.update(dbArgs)
        SCElement.finalize(self, dbArgs=args)
        return

    def getEmoteIconColor(self):
        if self.linkedEmoteEnabled() and not self.isWhispering():
            r, g, b = self.getColorScheme().getEmoteIconColor()
        else:
            r, g, b = self.getColorScheme().getEmoteIconDisabledColor()
        return (r,
         g,
         b,
         1)

    def updateEmoteIcon(self):
        if hasattr(self, 'button'):
            self.lastEmoteIconColor = self.getEmoteIconColor()
            for i in range(self.button['numStates']):
                self.button['image%s_image' % i].setColorScale(*self.lastEmoteIconColor)

        else:
            self.invalidate()

    def enterVisible(self):
        SCElement.enterVisible(self)
        if hasattr(self, 'lastEmoteIconColor'):
            if self.getEmoteIconColor() != self.lastEmoteIconColor:
                self.invalidate()

        def handleWhisperModeChange(whisperMode, self = self):
            if self.hasLinkedEmote():
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()

        self.accept(self.getEventName(SCWhisperModeChangeEvent), handleWhisperModeChange)

        def handleEmoteEnableStateChange(self = self):
            if self.hasLinkedEmote():
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()

        if self.hasLinkedEmote():
            if Emote.globalEmote:
                self.accept(Emote.globalEmote.EmoteEnableStateChanged, handleEmoteEnableStateChange)

    def exitVisible(self):
        SCElement.exitVisible(self)
        self.ignore(self.getEventName(SCWhisperModeChangeEvent))
        if Emote.globalEmote:
            self.ignore(Emote.globalEmote.EmoteEnableStateChanged)

    def getDisplayText(self):
        if self.getCharges() is not -1:
            return self.text + ' (%s)' % self.getCharges()
        else:
            return self.text
class DirectEntry(DirectFrame):
    directWtext = ConfigVariableBool('direct-wtext', 1)
    AllowCapNamePrefixes = ('Al', 'Ap', 'Ben', 'De', 'Del', 'Della', 'Delle', 'Der',
                            'Di', 'Du', 'El', 'Fitz', 'La', 'Las', 'Le', 'Les', 'Lo',
                            'Los', 'Mac', 'St', 'Te', 'Ten', 'Van', 'Von')
    ForceCapNamePrefixes = ("D'", 'DeLa', "Dell'", "L'", "M'", 'Mc', "O'")

    def __init__(self, parent=None, **kw):
        optiondefs = (
         (
          'pgFunc', PGEntry, None),
         ('numStates', 3, None),
         (
          'state', DGG.NORMAL, None),
         (
          'entryFont', None, DGG.INITOPT),
         (
          'width', 10, self.setup),
         (
          'numLines', 1, self.setup),
         (
          'focus', 0, self.setFocus),
         (
          'cursorKeys', 1, self.setCursorKeysActive),
         (
          'obscured', 0, self.setObscureMode),
         (
          'backgroundFocus', 0, self.setBackgroundFocus),
         (
          'initialText', '', DGG.INITOPT),
         ('command', None, None),
         (
          'extraArgs', [], None),
         ('failedCommand', None, None),
         (
          'failedExtraArgs', [], None),
         ('focusInCommand', None, None),
         (
          'focusInExtraArgs', [], None),
         ('focusOutCommand', None, None),
         (
          'focusOutExtraArgs', [], None),
         (
          'rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound),
         (
          'clickSound', DGG.getDefaultClickSound(), self.setClickSound),
         (
          'autoCapitalize', 0, self.autoCapitalizeFunc),
         (
          'autoCapitalizeAllowPrefixes', DirectEntry.AllowCapNamePrefixes, None),
         (
          'autoCapitalizeForcePrefixes', DirectEntry.ForceCapNamePrefixes, None))
        self.defineoptions(kw, optiondefs)
        DirectFrame.__init__(self, parent)
        if self['entryFont'] == None:
            font = DGG.getDefaultFont()
        else:
            font = self['entryFont']
        self.onscreenText = self.createcomponent('text', (), None, OnscreenText, (), parent=hidden, text='', align=TextNode.ALeft, font=font, scale=1, mayChange=1)
        self.onscreenText.removeNode()
        self.bind(DGG.ACCEPT, self.commandFunc)
        self.bind(DGG.ACCEPTFAILED, self.failedCommandFunc)
        self.accept(self.guiItem.getFocusInEvent(), self.focusInCommandFunc)
        self.accept(self.guiItem.getFocusOutEvent(), self.focusOutCommandFunc)
        self._autoCapListener = DirectObject()
        self.initialiseoptions(DirectEntry)
        if not hasattr(self, 'autoCapitalizeAllowPrefixes'):
            self.autoCapitalizeAllowPrefixes = DirectEntry.AllowCapNamePrefixes
        if not hasattr(self, 'autoCapitalizeForcePrefixes'):
            self.autoCapitalizeForcePrefixes = DirectEntry.ForceCapNamePrefixes
        for i in range(self['numStates']):
            self.guiItem.setTextDef(i, self.onscreenText.textNode)

        self.setup()
        self.unicodeText = 0
        if self['initialText']:
            self.enterText(self['initialText'])
        return

    def destroy(self):
        self.ignoreAll()
        self._autoCapListener.ignoreAll()
        DirectFrame.destroy(self)

    def setup(self):
        self.guiItem.setupMinimal(self['width'], self['numLines'])

    def setFocus(self):
        PGEntry.setFocus(self.guiItem, self['focus'])

    def setCursorKeysActive(self):
        PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])

    def setObscureMode(self):
        PGEntry.setObscureMode(self.guiItem, self['obscured'])

    def setBackgroundFocus(self):
        PGEntry.setBackgroundFocus(self.guiItem, self['backgroundFocus'])

    def setRolloverSound(self):
        rolloverSound = self['rolloverSound']
        if rolloverSound:
            self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound)
        else:
            self.guiItem.clearSound(DGG.ENTER + self.guiId)

    def setClickSound(self):
        clickSound = self['clickSound']
        if clickSound:
            self.guiItem.setSound(DGG.ACCEPT + self.guiId, clickSound)
        else:
            self.guiItem.clearSound(DGG.ACCEPT + self.guiId)

    def commandFunc(self, event):
        if self['command']:
            apply(self['command'], [self.get()] + self['extraArgs'])

    def failedCommandFunc(self, event):
        if self['failedCommand']:
            apply(self['failedCommand'], [self.get()] + self['failedExtraArgs'])

    def autoCapitalizeFunc(self):
        if self['autoCapitalize']:
            self._autoCapListener.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self._autoCapListener.accept(self.guiItem.getEraseEvent(), self._handleErasing)
        else:
            self._autoCapListener.ignore(self.guiItem.getTypeEvent())
            self._autoCapListener.ignore(self.guiItem.getEraseEvent())

    def focusInCommandFunc(self):
        if self['focusInCommand']:
            apply(self['focusInCommand'], self['focusInExtraArgs'])
        if self['autoCapitalize']:
            self.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self.accept(self.guiItem.getEraseEvent(), self._handleErasing)

    def _handleTyping(self, guiEvent):
        self._autoCapitalize()

    def _handleErasing(self, guiEvent):
        self._autoCapitalize()

    def _autoCapitalize(self):
        name = self.get().decode('utf-8')
        capName = ''
        wordSoFar = ''
        wasNonWordChar = True
        for i in xrange(len(name)):
            character = name[i]
            if string.lower(character) == string.upper(character) and character != "'":
                wordSoFar = ''
                wasNonWordChar = True
            else:
                capitalize = False
                if wasNonWordChar:
                    capitalize = True
                else:
                    if character == string.upper(character) and len(self.autoCapitalizeAllowPrefixes) and wordSoFar in self.autoCapitalizeAllowPrefixes:
                        capitalize = True
                    else:
                        if len(self.autoCapitalizeForcePrefixes) and wordSoFar in self.autoCapitalizeForcePrefixes:
                            capitalize = True
                if capitalize:
                    character = string.upper(character)
                else:
                    character = string.lower(character)
                wordSoFar += character
                wasNonWordChar = False
            capName += character

        self.enterText(capName.encode('utf-8'))

    def focusOutCommandFunc(self):
        if self['focusOutCommand']:
            apply(self['focusOutCommand'], self['focusOutExtraArgs'])
        if self['autoCapitalize']:
            self.ignore(self.guiItem.getTypeEvent())
            self.ignore(self.guiItem.getEraseEvent())

    def set(self, text):
        self.unicodeText = isinstance(text, types.UnicodeType)
        if self.unicodeText:
            self.guiItem.setWtext(text)
        else:
            self.guiItem.setText(text)

    def get(self, plain=False):
        wantWide = self.unicodeText or self.guiItem.isWtext()
        if not self.directWtext.getValue():
            wantWide = False
        if plain:
            if wantWide:
                return self.guiItem.getPlainWtext()
            return self.guiItem.getPlainText()
        else:
            if wantWide:
                return self.guiItem.getWtext()
            return self.guiItem.getText()

    def setCursorPosition(self, pos):
        if pos < 0:
            self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
        else:
            self.guiItem.setCursorPosition(pos)

    def enterText(self, text):
        self.set(text)
        self.setCursorPosition(self.guiItem.getNumCharacters())

    def getFont(self):
        return self.onscreenText.getFont()

    def getBounds(self, state=0):
        tn = self.onscreenText.textNode
        mat = tn.getTransform()
        align = tn.getAlign()
        lineHeight = tn.getLineHeight()
        numLines = self['numLines']
        width = self['width']
        if align == TextNode.ALeft:
            left = 0.0
            right = width
        else:
            if align == TextNode.ACenter:
                left = -width / 2.0
                right = width / 2.0
            else:
                if align == TextNode.ARight:
                    left = -width
                    right = 0.0
        bottom = -0.3 * lineHeight - lineHeight * (numLines - 1)
        top = lineHeight
        self.ll.set(left, 0.0, bottom)
        self.ur.set(right, 0.0, top)
        self.ll = mat.xformPoint(Point3.rfu(left, 0.0, bottom))
        self.ur = mat.xformPoint(Point3.rfu(right, 0.0, top))
        vec_right = Vec3.right()
        vec_up = Vec3.up()
        left = vec_right[0] * self.ll[0] + vec_right[1] * self.ll[1] + vec_right[2] * self.ll[2]
        right = vec_right[0] * self.ur[0] + vec_right[1] * self.ur[1] + vec_right[2] * self.ur[2]
        bottom = vec_up[0] * self.ll[0] + vec_up[1] * self.ll[1] + vec_up[2] * self.ll[2]
        top = vec_up[0] * self.ur[0] + vec_up[1] * self.ur[1] + vec_up[2] * self.ur[2]
        self.ll = Point3(left, 0.0, bottom)
        self.ur = Point3(right, 0.0, top)
        pad = self['pad']
        borderWidth = self['borderWidth']
        self.bounds = [self.ll[0] - pad[0] - borderWidth[0],
         self.ur[0] + pad[0] + borderWidth[0],
         self.ll[2] - pad[1] - borderWidth[1],
         self.ur[2] + pad[1] + borderWidth[1]]
        return self.bounds
Beispiel #7
0
class ServerLoginDialogMaker(object):
    USERNAME_CHARS = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789-')

    def __init__(self, app):
        self.app = app
        self.d = None
        self.dialog = None
        self.createAccountVar = None

        self.retypeLabel = None
        self.retypeBox = None
        self.usernameBox = None
        self.passwordBox = None
        self.focus = None

        self.do = DirectObject()

    @defer.inlineCallbacks
    def run(self, host, errorText=''):
        if errorText:
            yield self.showError(errorText)
        result = yield self._runMainDialog(host)
        defer.returnValue(result)

    def showError(self, text):
        if self.dialog:
            self.dialog.hide()
            self._unlinkHotkeys()

        def ok(result):
            dialog.cleanup()
            if self.dialog:
                self.dialog.show()
                self._linkHotkeys()
            d.callback(None)

        d = defer.Deferred()
        dialog = OkDialog(
            dialogName='joinError',
            text=text,
            command=ok,
            fadeScreen=0.3,
            button_pad=(0.1, 0.03),
            buttonPadSF=1.3,
        )
        dialog.show()
        return d

    def cancel(self):
        self.dialog.cleanup()
        self._unlinkHotkeys()
        self.dialog = None
        self.d = None

    def _linkHotkeys(self):
        self.do.accept('tab', self._tabPressed)
        self.do.accept('shift-tab', self._shiftTabPressed)
        self.do.accept('enter', self._enterPressed)

    def _setFocus(self, field):
        field['focus'] = True
        self.focus = field

    def _tabPressed(self):
        if self.focus is self.usernameBox:
            self._setFocus(self.passwordBox)
        elif self.focus is self.passwordBox and self.createAccountVar[0]:
            self._setFocus(self.retypeBox)
        else:
            self._setFocus(self.usernameBox)

    def _shiftTabPressed(self):
        if self.focus is self.passwordBox:
            self._setFocus(self.usernameBox)
        elif self.focus is self.usernameBox and self.createAccountVar[0]:
            self._setFocus(self.retypeBox)
        else:
            self._setFocus(self.passwordBox)

    def _enterPressed(self):
        self._buttonPressed(DGG.DIALOG_OK)

    def _unlinkHotkeys(self):
        self.do.ignoreAll()

    def _buttonPressed(self, result):
        if result == DGG.DIALOG_CANCEL:
            self._cleanupAndReturn(None)
            return

        username = self.usernameBox.get()
        if not username:
            self.showError('You must give a username!')
            return

        createAccount = self.createAccountVar[0]
        password = self.passwordBox.get()

        if createAccount:
            if any(c not in self.USERNAME_CHARS for c in username):
                self.showError('Invalid username!')
                return
            if password != self.retypeBox.get():
                self.showError('Passwords do not match!')
                return

        if not password:
            self.showError('Password cannot be blank!')
            return

        self.app.identitySettings.usernames[self.host] = username
        self._cleanupAndReturn((createAccount, username, password))

    def _cleanupAndReturn(self, value):
        self._unlinkHotkeys()
        self.dialog.cleanup()
        self.dialog = None

        d, self.d = self.d, None
        d.callback(value)

    def _radioChanged(self):
        if self.createAccountVar[0]:
            self.retypeLabel.show()
            self.retypeBox.show()
            self.retypeBox.enterText('')
        else:
            self.retypeLabel.hide()
            self.retypeBox.hide()

        if self.usernameBox.get() == '':
            self._setFocus(self.usernameBox)
        elif self.passwordBox.get() == '' or not self.createAccountVar[0]:
            self._setFocus(self.passwordBox)
        else:
            self._setFocus(self.retypeBox)

    def _runMainDialog(self, host):
        if self.d is not None:
            raise RuntimeError('already running')
        self.d = defer.Deferred()
        self.host = host

        self.dialog = OkCancelDialog(
            dialogName='hostGameQuery',
            command=self._buttonPressed,
            fadeScreen=0.3,
            button_pad=(0.1, 0.03),
            buttonPadSF=1.2,
            topPad=0.5,
            midPad=0.4,
            sidePad=0.42,
            pad=(0.05, 0.03),
        )

        DirectLabel(
            parent=self.dialog,
            text=host,
            text_scale=0.1,
            text_align=TextNode.ACenter,
            pos=(0, 0, 0.42),
        )

        def makeEntryField(caption, z, obscured):
            label = DirectLabel(
                parent=self.dialog,
                text=caption,
                text_scale=0.05,
                text_align=TextNode.ALeft,
                pos=(-0.82, 0, z + 0.1),
            )
            entry = DirectEntry(
                parent=self.dialog,
                scale=0.05,
                width=32,
                pos=(-0.8, 0, z),
                pad=(0.2, 0.1),
                obscured=obscured,
                relief=DGG.GROOVE,
            )
            return label, entry

        discard, self.usernameBox = makeEntryField('Uesrname', 0.1, False)
        discard, self.passwordBox = makeEntryField('Password', -0.1, True)
        self.retypeLabel, self.retypeBox = makeEntryField(
            'Retype password', -0.3, True)

        def makeRadioButton(text, x, value):
            return DirectRadioButton(
                parent=self.dialog,
                text=text,
                variable=self.createAccountVar,
                value=[value],
                scale=0.05,
                text_align=TextNode.ACenter,
                pos=(x, 0, 0.32),
                pad=(0.2, 0.1),
                relief=None,
                command=self._radioChanged,
            )

        self.createAccountVar = [False]
        radioButtons = [
            makeRadioButton('Sign in', -0.3, False),
            makeRadioButton('New account', 0.3, True),
        ]
        for button in radioButtons:
            button.setOthers(radioButtons)

        username = self.app.identitySettings.usernames.get(host)
        if username is not None:
            self.usernameBox.enterText(username)
            self._setFocus(self.passwordBox)
        else:
            self._setFocus(self.usernameBox)

        self._linkHotkeys()
        self.dialog.show()
        return self.d
Beispiel #8
0
class SkillShotScreen(GameScreen):

    awards = [
        "QUICK FREEZE", "ADVANCE BONUS X", "5000 POINTS", "SPOT MTL",
        "QUICK FREEZE", "500,000 POINTS", "LIGHT CLAW"
    ]

    current_side = 1
    current_award = 0
    cube = None
    _awardText = None
    cubeMovement = None
    cubeRotation = None
    awardMovement = None
    SKILLSHOT_THRESHOLD = 2.34726002216

    def __init__(self, screen_manager):
        super(SkillShotScreen, self).__init__(screen_manager,
                                              "skillshot_screen")

        # Load our trunk model
        self.cube = base.loader.loadModel("assets/models/powerup.egg")
        # Set its position to:
        # X: -0.4 (just to the left of h-center)
        # Y: 40 (way far back away from the viewer)
        # Z: 0 (vertically center)
        self.cube.setPos(11, 40, 0)
        # Set the scaling size of the trunk
        self.cube.setScale(0.05, 0.05, 0.05)

        if base.displayFlipped:
            self.cube.setAttrib(
                CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))

        # Insert the trunk into this screen's rendering tree
        self.cube.reparentTo(self.node)
        """
        # Set up a splotlight to project onto the trunk
        self.dlight = Spotlight('my dlight')
        # Associate the spotlight to the rendering system
        self.dlnp = render.attachNewNode(self.dlight)
        # Set this trunk's light explicitly so it only takes light from this light
        self.cube.setLight(self.dlnp)
        self.dlnp.lookAt(self.cube)
        
        plight = PointLight('plight')
        plight.setColor(VBase4(1, 1, 1, 1))
        self.plnp = render.attachNewNode(plight)
        self.plnp.setPos(11, 35, 12)
        """

        self.dlight = DirectionalLight('dlight')
        self.dlight.setColor(VBase4(1, 1, 1, 1))
        self.dlnp = render.attachNewNode(self.dlight)
        self.dlnp.setHpr(0, -60, 0)
        self.dlnp.lookAt(self.cube)

        self.obj = DirectObject()

        self.movement_speed = 0.7
        """
        self.explosion = Sprite(
                                parent=self.node, 
                                file_name="assets/sprites/explosion/explosion_", 
                                file_type="png", 
                                num_frames=29, 
                                int_padding=2,
                                scale=(5,5,5))
        """

        self.explosion = Sprite(
            parent=self.node,
            file_name="assets/sprites/blue_explosion/blue_explosion_",
            file_type="png",
            num_frames=13,
            int_padding=2,
            scale=(5, 5, 5))
        #0.6 - high
        #-0.4 - low
        self._awardText = OnscreenText(
            "RANDOM AWARD",
            1,
            font=base.fontLoader.load('digital.ttf'),
            fg=((0.0 / 255.0), (255.0 / 255.0), (255.0 / 255.0), 1),
            pos=(1, -0.4),
            align=TextNode.ACenter,
            scale=.1,
            mayChange=True,
            parent=self.node2d)

        self.cubeRotation = self.cube.hprInterval(1.2, Vec3(360, 0, 0))
        # Set up a sequence to perform the animation in, pause and out... "sequentially"
        self.cubeMovement = Sequence(
            # Lerp stands for "linearly interpolate", so we move from one position to the other with
            # an 'easeOut' blend so it comes to a nice slow stop at the end instead of an abrupt finish
            LerpPosInterval(self.cube,
                            self.movement_speed,
                            pos=(13, 40, 11),
                            startPos=(13, 40, 0),
                            blendType='easeOut'),
            # Animate back to our home position (off screen) with an ease in so it starts moving gradually
            LerpPosInterval(self.cube,
                            self.movement_speed,
                            pos=(13, 40, 0),
                            blendType='easeIn'))
        self.awardMovement = Sequence(
            LerpFunc(self._updateAwardTextPosition,
                     fromData=-0.4,
                     toData=0.6,
                     duration=self.movement_speed,
                     blendType='easeOut',
                     extraArgs=[],
                     name=None),
            LerpFunc(self._updateAwardTextPosition,
                     fromData=0.6,
                     toData=-0.4,
                     duration=self.movement_speed,
                     blendType='easeIn',
                     extraArgs=[],
                     name=None))

    def _updateAwardTextPosition(self, t):
        self._awardText.setPos(1, t)

    def show(self):
        """
        Overridden method that is invoked when the screen is shown.
        
        We use this to orient our camera and spotlights to the appropriate place. We do this because
        not every screen has its own camera. I suppose we could do that in the future the same way that
        each screen has its own 2d and 3d nodes.
        """

        # Set the camera a bit up in the air (vertically, not moving forward. Just straight up)
        base.camera.setZ(9.6)
        # Set the pitch negatively a bit so we can look down upon the trunk
        base.camera.setP(-5)

        # Call our base class show method now so it can render anything it wants
        super(SkillShotScreen, self).show()

        # Fire off the sequence
        self.cubeMovement.loop()
        self.cubeRotation.loop()
        self.awardMovement.loop()
        base.taskMgr.doMethodLater(0.1, self._advanceAward, 'award_advance')

        self.cube.setLight(self.dlnp)
        random.shuffle(self.awards)
        self.obj.ignoreAll()
        self.obj.acceptOnce("skillshot_hit", self.stop_skillshot_motion)
        self.cube.show()
        self._awardText.show()

    def stop_skillshot_motion(self):

        self.explosion = Sprite(
            parent=self.node,
            file_name="assets/sprites/blue_explosion/blue_explosion_",
            file_type="png",
            num_frames=13,
            int_padding=2,
            scale=(5, 5, 5))

        self.cubeRotation.pause()
        self.cubeMovement.pause()
        self.awardMovement.pause()
        base.taskMgr.remove('award_advance')
        self.cube.hide()
        self._awardText.hide()
        self.explosion.setPos(self.cube.getX() - 0.50, 40, self.cube.getZ())
        self.explosion.fps = 24
        self.explosion.play(loops=1)
        # -0.3550 is the threshold
        if self.cube.getZ() <= self.SKILLSHOT_THRESHOLD:

            base.screenManager.showModalMessage(
                message=self.awards[self.current_award],
                time=5.0,
                font="eurostile.ttf",
                scale=0.08,
                bg=(0, 0, 0, 1),
                fg=(0, 1, 1, 1),
                frame_color=(0, 1, 1, 1),
                blink_speed=0.015,
                blink_color=(0, 0, 0, 1),
                #l r t b
                frame_margin=(0.1, 0.25, 0, 0),
                animation='slide',
                start_location=(1.7, 0, 0.8),
                end_location=(1, 0, 0.8))

    def is_skillshot_hit(self):
        if self.cube.getZ() <= self.SKILLSHOT_THRESHOLD:
            return self.awards[self.current_award]
        else:
            return False

    def _advanceAward(self, task):
        self.current_award = (self.current_award + 1) % len(self.awards)
        self._awardText.setText(self.awards[self.current_award])
        return task.again

    def hide(self):
        """
        Called when the screen manager wants to remove this screen from the display.
        
        We have no choice but to obey when this method is called. So hide our background.
        
        Our models will be hidden automatically as the 3d nodes are removed from the render tree
        """
        super(SkillShotScreen, self).hide()

        if self.cube != None: self.cube.clearLight(self.dlnp)
        if self.cubeMovement: self.cubeMovement.finish()
        if self.cubeRotation: self.cubeRotation.finish()
        if self.awardMovement: self.awardMovement.finish()
        base.taskMgr.remove('award_advance')
        if self._awardText != None: self._awardText.hide()
Beispiel #9
0
class SCTerminal(SCElement):
    """ SCTerminal is the base class for all 'terminal' speedchat
    entities """
    def __init__(self, linkedEmote=None):
        SCElement.__init__(self)
        self.setLinkedEmote(linkedEmote)

        scGui = loader.loadModel(SCMenu.GuiModelName)
        self.emotionIcon = scGui.find('**/emotionIcon')
        self.setDisabled(False)
        self.__numCharges = -1

        # should we listen for whisper mode changes?
        self._handleWhisperModeSV = StateVar(False)
        # can't set this up until we're ready to have the handler func called
        self._handleWhisperModeFC = None

    def destroy(self):
        self._handleWhisperModeSV.set(False)
        if self._handleWhisperModeFC:
            self._handleWhisperModeFC.destroy()
        self._handleWhisperModeSV.destroy()
        SCElement.destroy(self)

    def privSetSettingsRef(self, settingsRef):
        SCElement.privSetSettingsRef(self, settingsRef)
        if self._handleWhisperModeFC is None:
            self._handleWhisperModeFC = FunctionCall(self._handleWhisperModeSVChanged,
                                                     self._handleWhisperModeSV)
            self._handleWhisperModeFC.pushCurrentState()
        # if this terminal is not whisperable, we need to listen for whisper mode changes
        self._handleWhisperModeSV.set((self.settingsRef is not None) and
                                      (not self.isWhisperable()))

    def _handleWhisperModeSVChanged(self, handleWhisperMode):
        if handleWhisperMode:
            # this terminal can't be whispered. we need to reconstruct
            # our GUI element when the whisper mode changes
            # listen for that mode change
            # create a DirectObject to avoid conflicts with other parts of this
            # object that are listening for this event
            self._wmcListener = DirectObject()
            self._wmcListener.accept(self.getEventName(SCWhisperModeChangeEvent),
                                     self._handleWhisperModeChange)
        else:
            if hasattr(self, '_wmcListener'):
                # we no longer need to listen for whisper mode changes
                self._wmcListener.ignoreAll()
                del self._wmcListener
                # make sure our GUI element is appropriate
                self.invalidate()

    def _handleWhisperModeChange(self, whisperMode):
        # whisper mode changed, we need to change our GUI element
        self.invalidate()

    # the meat of SCTerminal; inheritors should override this
    # and perform the appropriate action
    def handleSelect(self):
        """ called when the user selects this node """
        # send the generic 'something was selected' event
        messenger.send(self.getEventName(SCTerminalSelectedEvent))

        # if we have a linked emote, and it isn't disabled, generate a msg
        if self.hasLinkedEmote() and self.linkedEmoteEnabled():
                messenger.send(self.getEventName(SCTerminalLinkedEmoteEvent),
                               [self.linkedEmote])

    def isWhisperable(self):
        # can this terminal be sent as a whisper message?
        return True

    # Some terminal nodes have an emote associated with them, which
    # should be invoked when the node is selected.
    def getLinkedEmote(self):
        return self.linkedEmote
    def setLinkedEmote(self, linkedEmote):
        self.linkedEmote = linkedEmote
        # TODO: we should make sure we're listening for emote
        # enable state changes if this is set while we're visible
        self.invalidate()
    def hasLinkedEmote(self):
        return (self.linkedEmote is not None)
    def linkedEmoteEnabled(self):
        if Emote.globalEmote:
            return Emote.globalEmote.isEnabled(self.linkedEmote)

    def getCharges(self):
        return self.__numCharges
    
    def setCharges(self, nCharges):
        self.__numCharges = nCharges
        if (nCharges is 0):
            self.setDisabled(True)
    
    # support for disabled terminals
    def isDisabled(self):
        return self.__disabled or (self.isWhispering() and not self.isWhisperable())

    def setDisabled(self, bDisabled):
        # make the button 'unclickable'
        self.__disabled = bDisabled

    # from SCElement
    def onMouseClick(self, event):
        if not self.isDisabled():
            SCElement.onMouseClick(self, event)
            self.handleSelect()

    def getMinDimensions(self):
        width, height = SCElement.getMinDimensions(self)
        if self.hasLinkedEmote():
            # add space for the emotion icon
            width += 1.3
        return width, height

    def finalize(self, dbArgs={}):
        """ catch this call and influence the appearance of our button """
        if not self.isDirty():
            return

        args = {}

        if self.hasLinkedEmote():
            self.lastEmoteIconColor = self.getEmoteIconColor()
            self.emotionIcon.setColorScale(*self.lastEmoteIconColor)
            args.update({
                'image':         self.emotionIcon,
                'image_pos':     (self.width-.6,0,-self.height*.5),
                })

        if self.isDisabled():
            args.update({
                'rolloverColor': (0,0,0,0),
                'pressedColor': (0,0,0,0),
                'rolloverSound': None,
                'clickSound': None,
                'text_fg': self.getColorScheme().getTextDisabledColor()+(1,),
                })

        args.update(dbArgs)
        SCElement.finalize(self, dbArgs=args)

    def getEmoteIconColor(self):
        if self.linkedEmoteEnabled() and (not self.isWhispering()):
            r,g,b = self.getColorScheme().getEmoteIconColor()
        else:
            r,g,b = self.getColorScheme().getEmoteIconDisabledColor()
        return (r,g,b,1)

    def updateEmoteIcon(self):
        if hasattr(self, 'button'):
            self.lastEmoteIconColor = self.getEmoteIconColor()
            for i in range(self.button['numStates']):
                self.button['image%s_image' % i].setColorScale(
                    *self.lastEmoteIconColor)
        else:
            self.invalidate()

    # from SCObject
    def enterVisible(self):
        SCElement.enterVisible(self)

        # Check if the emote state has changed since the last time
        # we were finalized, and invalidate if it's different.
        if hasattr(self, 'lastEmoteIconColor'):
            if self.getEmoteIconColor() != self.lastEmoteIconColor:
                self.invalidate()

        # listen for whisper-mode changes
        def handleWhisperModeChange(whisperMode, self=self):
            if self.hasLinkedEmote():
                # we are leaving or entering whisper mode;
                # the appearance of our emote icon needs to change
                # (no linked emotes on whispers)
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()
        self.accept(self.getEventName(SCWhisperModeChangeEvent),
                    handleWhisperModeChange)

        # listen for emote-enable state changes
        def handleEmoteEnableStateChange(self=self):
            if self.hasLinkedEmote():
                # emotions have just become enabled/disabled
                # update our emote icon
                # (no emotes when whispering)
                if self.isVisible() and not self.isWhispering():
                    self.updateEmoteIcon()
        if self.hasLinkedEmote():
            if Emote.globalEmote:
                self.accept(Emote.globalEmote.EmoteEnableStateChanged,
                            handleEmoteEnableStateChange)

    def exitVisible(self):
        SCElement.exitVisible(self)
        self.ignore(self.getEventName(SCWhisperModeChangeEvent))
        if Emote.globalEmote:
            self.ignore(Emote.globalEmote.EmoteEnableStateChanged)

    def getDisplayText(self):
        if self.getCharges() is not -1:
            return self.text + " (%s)" % self.getCharges()
        else:
            return self.text
Beispiel #10
0
class CreditsReel(object):
    '''
    Creates a Panda scene that's independent of the main render tree, so that
    the credits can be displayed in a smaller display region within the main
    screen.
    '''
    def __init__(self, app):
        self.app = app
        self.do = DirectObject()

        self.running = False
        self.displayRegion = None
        self.root = NodePath('creditsRender')
        self.camera = Camera('creditsCam')
        self.cameraNP = NodePath(self.camera)

        # Set parameters to match those of render2d
        self.root.setDepthTest(0)
        self.root.setDepthWrite(0)
        self.root.setMaterialOff(1)
        self.root.setTwoSided(1)

        self.aspect2d = self.root  # self.root.attachNewNode('creditsAspect')

        lens = OrthographicLens()
        lens.setFilmSize(2, 2)
        lens.setFilmOffset(0, 0)
        lens.setNearFar(-1000, 1000)
        self.camera.setLens(lens)

        self.cameraNP.reparentTo(self.root)

        self.scrollTask = None
        self.lastTime = None
        self.creditsFileLoaded = False

    def loadCreditsFileIfNeeded(self):
        if self.creditsFileLoaded:
            return

        filePath = getPath(startupMenu, 'credits3d.txt')
        with codecs.open(filePath, 'rU', encoding='utf-8') as f:
            creditsLines = f.read().splitlines()

        y = 0
        for line in creditsLines:
            if line.startswith('!!'):
                font = self.app.fonts.creditsH1
                line = line[len('!!'):]
            elif line.startswith('!'):
                font = self.app.fonts.creditsH2
                line = line[len('!'):]
            else:
                font = self.app.fonts.creditsFont

            lineHeight = font.getPandaLineHeight(self.app)
            node = font.makeOnscreenText(
                self.app,
                text=line or ' ',  # Prevents .getNumRows() bug
                fg=self.app.theme.colours.mainMenuColour,
                align=TextNode.ACenter,
                pos=(0, y - lineHeight),
                parent=self.aspect2d,
                wordwrap=28,
            )
            y -= lineHeight * (node.textNode.getNumRows() + 0.2)

        self.creditsFileLoaded = True

    def stop(self):
        if not self.running:
            return
        self.running = False
        self.do.ignoreAll()

        if self.scrollTask is not None:
            self.app.panda.taskMgr.remove(self.scrollTask)
            self.scrollTask = None

        if self.displayRegion is not None:
            self.displayRegion.setActive(False)

    def start(self):
        self.cameraNP.setPos((0, 0, 0))
        if self.running:
            return
        self.running = True

        self.loadCreditsFileIfNeeded()

        if self.scrollTask is None:
            self.scrollTask = self.app.panda.taskMgr.add(
                self.updateCredits, 'creditsLoop')
            self.lastTime = self.scrollTask.time

        if self.displayRegion is None:
            self.displayRegion = self.app.panda.win.makeDisplayRegion()
            self.displayRegion.setSort(5)
            self.displayRegion.setClearDepthActive(1)
            self.displayRegion.setIncompleteRender(False)
            self.displayRegion.setCamera(self.cameraNP)

        self.displayRegion.setActive(True)
        self.do.accept('aspectRatioChanged', self.aspectRatioChanged)
        self.aspectRatioChanged()

    def aspectRatioChanged(self):
        # Scaling from -1 to +1
        idealTop = 0.66
        idealBottom = -0.74

        aspectRatio = self.app.panda.getAspectRatio()
        top = idealTop * min(aspectRatio * 3. / 4, 1)
        bottom = idealBottom * min(aspectRatio * 3. / 4, 1)

        self.displayRegion.setDimensions(0, 1, 0.5 * (1 + bottom),
                                         0.5 * (1 + top))

        windowRatio = 2 * aspectRatio / (top - bottom)

        self.cameraNP.setScale(windowRatio * 3. / 4, 1.0, 1.0)

    def updateCredits(self, task):
        deltaT = task.time - self.lastTime
        self.lastTime = task.time

        z = self.cameraNP.getZ()
        z -= deltaT * CREDITS_SCROLL_SPEED
        self.cameraNP.setZ(z)
        return Task.cont
Beispiel #11
0
class ControlSettingsScreenHelper(object):

    COLOUR_VALID = (1, 1, 1, 1)
    COLOUR_SELECTED = (0.75, 0.75, 1, 1)
    COLOUR_INVALID = (1, 0.75, 0.75, 1)

    def __init__(self, app, scene, parent):
        self.app = app
        self.scene = scene
        self.parent = parent
        self.keymap = self.app.keymap
        self.do = DirectObject()

        self.saveButton = None
        self.finishButton = None
        self.pendingChanges = False

        # The KeyboadMapping provides a dict of keys to actions: this member
        # does the opposite (provides a dict to actions to keys)
        self.keys = {}

        self.selectedAction = None
        self.inputLookup = {}
        self.layout = []

    def show(self):
        self.keys = dict((v, k) for k, v in self.keymap.actions.items())

        for column in self.layout:
            for category in column:
                for action in category:
                    if action in self.keys:
                        self.inputLookup[action]['text'] = self.keys[action]
                        self.inputLookup[action]['frameColor'] = self.COLOUR_VALID
                    else:
                        self.inputLookup[action]['frameColor'] = self.COLOUR_INVALID
                        self.keys[action] = None

    def setup(self, node):
        colours = self.app.theme.colours

        TEXT_PROPERTIES = {
            'parent': node,
            'text_scale': 0.038,
            'text_fg': colours.listboxButtons,
            'text_align': TextNode.A_right,
            'relief': None,
        }

        KEY_PROPERTIES = {
            'parent': node,
            'scale': 0.038,
            'frameColor': self.COLOUR_VALID,
            'frameSize': (-3.0, 3.0, -0.7, 0.7),
            'text_align': TextNode.A_center,
            'text_scale': 0.9,
            'text_pos': (0, -0.18),
            'relief': DGG.FLAT,
            'textMayChange': True,
            'command': self.actionSelected,
        }

        movement = [ACTION_JUMP, ACTION_DOWN, ACTION_LEFT, ACTION_RIGHT]
        menus = [ACTION_MAIN_MENU, ACTION_MORE_MENU]
        actions = [
            ACTION_UPGRADE_MENU, ACTION_USE_UPGRADE,
            ACTION_ABANDON_UPGRADE, ACTION_EDIT_PLAYER_INFO, ACTION_READY,
            ACTION_PAUSE_GAME, ACTION_EMOTE,
        ]
        misc = [ACTION_CHAT, ACTION_FOLLOW]
        upgrades = [
            upgradeClass.action for upgradeClass in sorted(
                allUpgrades, key=lambda upgradeClass: upgradeClass.order)]
        upgrades.append(ACTION_CLEAR_UPGRADE)

        display = [
            ACTION_LEADERBOARD_TOGGLE, ACTION_HUD_TOGGLE,
            ACTION_TERMINAL_TOGGLE]

        actionNames = {
            ACTION_ABANDON_UPGRADE: 'Abandon upgrade',
            ACTION_UPGRADE_MENU: 'Select upgrade',
            ACTION_USE_UPGRADE: 'Activate upgrade',
            ACTION_EDIT_PLAYER_INFO: 'Change nick / hat',
            ACTION_CHAT: 'Chat',
            ACTION_DOWN: 'Drop down',
            ACTION_EMOTE: 'Emote',
            ACTION_FOLLOW: 'Auto pan (replay)',
            ACTION_JUMP: 'Jump',
            ACTION_LEADERBOARD_TOGGLE: 'Show leaderboard',
            ACTION_LEFT: 'Move left',
            ACTION_MAIN_MENU: 'Main menu',
            ACTION_MORE_MENU: 'Advanced',
            ACTION_CLEAR_UPGRADE: 'Deselect upgrade',
            ACTION_READY: 'Toggle ready',
            ACTION_PAUSE_GAME: 'Pause/resume',
            ACTION_RIGHT: 'Move right',
            ACTION_HUD_TOGGLE: 'Toggle HUD',
            ACTION_TERMINAL_TOGGLE: 'Toggle terminal',
        }
        actionNames.update((upgradeClass.action, upgradeClass.name) for
                           upgradeClass in allUpgrades)

        # Organise the categories by column
        self.layout = [
            [movement, menus],
            [actions, display],
            [upgrades, misc],
        ]

        xPos = -0.68

        for column in self.layout:          # Each column
            yPos = 0.30
            for category in column:         # Each category
                for action in category:     # Each action
                    # Draw action name (eg. Respawn)
                    label = DirectLabel(
                        text=actionNames[action],
                        **TEXT_PROPERTIES
                    )
                    align(label, right=xPos, midZ=yPos)

                    # Create input box
                    box = DirectButton(
                        text='',
                        extraArgs=[action],
                        **KEY_PROPERTIES
                    )
                    align(box, left=xPos + 0.03, midZ=yPos)
                    self.inputLookup[action] = box

                    yPos -= 0.07  # Between items
                yPos -= 0.08      # Between categories
            xPos += 0.65          # Between columns

        BUTTON_PROPERTIES = {
            'scale': 0.04,
            'frameSize': (-5.0, 5.0, -1.0, 1.5),
            'parent': node,
        }

        self.restoreDefaultButton = DirectButton(
            text='Restore defaults',
            # scale=0.04,
            # parent=node,
            command=self.restoreDefaults,
            # text_align=TextNode.A_left,
            # pad=(0.5, 0.2)
            **BUTTON_PROPERTIES
        )
        align(self.restoreDefaultButton, midX=0, z=-0.63)

        self.saveButton = DirectButton(
            text='Save',
            command=self.save,
            **BUTTON_PROPERTIES
        )
        align(self.saveButton, left=-0.87, z=-0.63)
        self.saveButton.hide()

        self.finishButton = DirectButton(
            text='Back',
            command=self.cancelPressed,
            **BUTTON_PROPERTIES
        )
        align(self.finishButton, right=0.87, z=-0.63)

    def cancelPressed(self):
        self.deselectAction()
        if self.pendingChanges:
            self.keymap.reset()
            self.show()
            self.hideSaveButton()
        else:
            self.parent.showMainButtons()

    def save(self):
        self.keymap.apply()
        self.keymap.save()
        self.hideSaveButton()

    def showSaveButton(self):
        self.pendingChanges = True
        self.saveButton.show()
        self.finishButton['text'] = 'Cancel'

    def hideSaveButton(self):
        self.pendingChanges = False
        self.saveButton.hide()
        self.finishButton['text'] = 'Back'

    def restoreDefaults(self):
        self.deselectAction()
        self.keymap.revertToDefault()
        self.show()
        self.showSaveButton()

    def deselectAction(self):
        if self.selectedAction:
            self.inputLookup[self.selectedAction]['frameColor'] = self.COLOUR_VALID
        self.selectedAction = None
        self.do.ignoreAll()

    def actionSelected(self, action):
        self.deselectAction()
        self.selectedAction = action
        button = self.inputLookup[action]
        button['frameColor'] = self.COLOUR_SELECTED
        log.debug('Changing key for %s' % action)

        self.app.panda.buttonThrowers[0].node().setButtonDownEvent('button')
        self.do.accept('button', self.keyPressed, [action])

    def keyPressed(self, action, key):
        self.deselectAction()

        oldKey = self.keys[action]

        if oldKey == key:
            return

        # Remove the old key from the keymap
        if oldKey is not None:
            del self.keymap.actions[oldKey]

        self.inputLookup[action]['text'] = key

        # If there's a conflict, remove the conflicting action from the keymap
        if key in self.keymap.actions:
            secondAction = self.keymap.actions[key]
            log.debug('Overwriting conflicting key for %s' % secondAction)

            self.inputLookup[secondAction]['text'] = ''
            self.inputLookup[secondAction]['frameColor'] = self.COLOUR_INVALID
            self.keys[secondAction] = None

        # Update the keymap with the new key
        self.keys[action] = key
        self.keymap.actions[key] = action
        log.debug('New key for %s is %s' % (action, key))

        self.showSaveButton()
Beispiel #12
0
class DirectEntry(DirectFrame):
    __module__ = __name__
    directWtext = ConfigVariableBool('direct-wtext', 1)
    AllowCapNamePrefixes = ('Al', 'Ap', 'Ben', 'De', 'Del', 'Della', 'Delle', 'Der', 'Di', 'Du', 'El', 'Fitz', 'La', 'Las', 'Le', 'Les', 'Lo', 'Los', 'Mac', 'St', 'Te', 'Ten', 'Van', 'Von')
    ForceCapNamePrefixes = ("D'", 'DeLa', "Dell'", "L'", "M'", 'Mc', "O'")

    def __init__(self, parent = None, **kw):
        optiondefs = (('pgFunc', PGEntry, None),
         ('numStates', 3, None),
         ('state', DGG.NORMAL, None),
         ('entryFont', None, DGG.INITOPT),
         ('width', 10, self.setup),
         ('numLines', 1, self.setup),
         ('focus', 0, self.setFocus),
         ('cursorKeys', 1, self.setCursorKeysActive),
         ('obscured', 0, self.setObscureMode),
         ('backgroundFocus', 0, self.setBackgroundFocus),
         ('initialText', '', DGG.INITOPT),
         ('command', None, None),
         ('extraArgs', [], None),
         ('failedCommand', None, None),
         ('failedExtraArgs', [], None),
         ('focusInCommand', None, None),
         ('focusInExtraArgs', [], None),
         ('focusOutCommand', None, None),
         ('focusOutExtraArgs', [], None),
         ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound),
         ('clickSound', DGG.getDefaultClickSound(), self.setClickSound),
         ('autoCapitalize', 0, self.autoCapitalizeFunc),
         ('autoCapitalizeAllowPrefixes', DirectEntry.AllowCapNamePrefixes, None),
         ('autoCapitalizeForcePrefixes', DirectEntry.ForceCapNamePrefixes, None))
        self.defineoptions(kw, optiondefs)
        DirectFrame.__init__(self, parent)
        if self['entryFont'] == None:
            font = DGG.getDefaultFont()
        else:
            font = self['entryFont']
        self.onscreenText = self.createcomponent('text', (), None, OnscreenText, (), parent=hidden, text='', align=TextNode.ALeft, font=font, scale=1, mayChange=1)
        self.onscreenText.removeNode()
        self.bind(DGG.ACCEPT, self.commandFunc)
        self.bind(DGG.ACCEPTFAILED, self.failedCommandFunc)
        self.accept(self.guiItem.getFocusInEvent(), self.focusInCommandFunc)
        self.accept(self.guiItem.getFocusOutEvent(), self.focusOutCommandFunc)
        self._autoCapListener = DirectObject()
        self.initialiseoptions(DirectEntry)
        if not hasattr(self, 'autoCapitalizeAllowPrefixes'):
            self.autoCapitalizeAllowPrefixes = DirectEntry.AllowCapNamePrefixes
        if not hasattr(self, 'autoCapitalizeForcePrefixes'):
            self.autoCapitalizeForcePrefixes = DirectEntry.ForceCapNamePrefixes
        for i in range(self['numStates']):
            self.guiItem.setTextDef(i, self.onscreenText.textNode)

        self.setup()
        self.unicodeText = 0
        if self['initialText']:
            self.enterText(self['initialText'])
        return None

    def destroy(self):
        self.ignoreAll()
        self._autoCapListener.ignoreAll()
        DirectFrame.destroy(self)

    def setup(self):
        self.guiItem.setupMinimal(self['width'], self['numLines'])

    def setFocus(self):
        PGEntry.setFocus(self.guiItem, self['focus'])

    def setCursorKeysActive(self):
        PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])

    def setObscureMode(self):
        PGEntry.setObscureMode(self.guiItem, self['obscured'])

    def setBackgroundFocus(self):
        PGEntry.setBackgroundFocus(self.guiItem, self['backgroundFocus'])

    def setRolloverSound(self):
        rolloverSound = self['rolloverSound']
        if rolloverSound:
            self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound)
        else:
            self.guiItem.clearSound(DGG.ENTER + self.guiId)

    def setClickSound(self):
        clickSound = self['clickSound']
        if clickSound:
            self.guiItem.setSound(DGG.ACCEPT + self.guiId, clickSound)
        else:
            self.guiItem.clearSound(DGG.ACCEPT + self.guiId)

    def commandFunc(self, event):
        if self['command']:
            apply(self['command'], [self.get()] + self['extraArgs'])

    def failedCommandFunc(self, event):
        if self['failedCommand']:
            apply(self['failedCommand'], [self.get()] + self['failedExtraArgs'])

    def autoCapitalizeFunc(self):
        if self['autoCapitalize']:
            self._autoCapListener.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self._autoCapListener.accept(self.guiItem.getEraseEvent(), self._handleErasing)
        else:
            self._autoCapListener.ignore(self.guiItem.getTypeEvent())
            self._autoCapListener.ignore(self.guiItem.getEraseEvent())

    def focusInCommandFunc(self):
        if self['focusInCommand']:
            apply(self['focusInCommand'], self['focusInExtraArgs'])
        if self['autoCapitalize']:
            self.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self.accept(self.guiItem.getEraseEvent(), self._handleErasing)

    def _handleTyping(self, guiEvent):
        self._autoCapitalize()

    def _handleErasing(self, guiEvent):
        self._autoCapitalize()

    def _autoCapitalize(self):
        name = self.get().decode('utf-8')
        capName = ''
        wordSoFar = ''
        wasNonWordChar = True
        for i in xrange(len(name)):
            character = name[i]
            if string.lower(character) == string.upper(character) and character != "'":
                wordSoFar = ''
                wasNonWordChar = True
            else:
                capitalize = False
                if wasNonWordChar:
                    capitalize = True
                elif character == string.upper(character) and len(self.autoCapitalizeAllowPrefixes) and wordSoFar in self.autoCapitalizeAllowPrefixes:
                    capitalize = True
                elif len(self.autoCapitalizeForcePrefixes) and wordSoFar in self.autoCapitalizeForcePrefixes:
                    capitalize = True
                if capitalize:
                    character = string.upper(character)
                else:
                    character = string.lower(character)
                wordSoFar += character
                wasNonWordChar = False
            capName += character

        self.enterText(capName.encode('utf-8'))

    def focusOutCommandFunc(self):
        if self['focusOutCommand']:
            apply(self['focusOutCommand'], self['focusOutExtraArgs'])
        if self['autoCapitalize']:
            self.ignore(self.guiItem.getTypeEvent())
            self.ignore(self.guiItem.getEraseEvent())

    def set(self, text):
        self.unicodeText = isinstance(text, types.UnicodeType)
        if self.unicodeText:
            self.guiItem.setWtext(text)
        else:
            self.guiItem.setText(text)

    def get(self, plain = False):
        if not self.unicodeText:
            wantWide = self.guiItem.isWtext()
            if not self.directWtext.getValue():
                wantWide = False
            if plain:
                return wantWide and self.guiItem.getPlainWtext()
            else:
                return self.guiItem.getPlainText()
        elif wantWide:
            return self.guiItem.getWtext()
        else:
            return self.guiItem.getText()

    def setCursorPosition(self, pos):
        if pos < 0:
            self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
        else:
            self.guiItem.setCursorPosition(pos)

    def enterText(self, text):
        self.set(text)
        self.setCursorPosition(self.guiItem.getNumCharacters())

    def getFont(self):
        return self.onscreenText.getFont()

    def getBounds(self, state = 0):
        tn = self.onscreenText.textNode
        mat = tn.getTransform()
        align = tn.getAlign()
        lineHeight = tn.getLineHeight()
        numLines = self['numLines']
        width = self['width']
        if align == TextNode.ALeft:
            left = 0.0
            right = width
        elif align == TextNode.ACenter:
            left = -width / 2.0
            right = width / 2.0
        elif align == TextNode.ARight:
            left = -width
            right = 0.0
        bottom = -0.3 * lineHeight - lineHeight * (numLines - 1)
        top = lineHeight
        self.ll.set(left, 0.0, bottom)
        self.ur.set(right, 0.0, top)
        self.ll = mat.xformPoint(self.ll)
        self.ur = mat.xformPoint(self.ur)
        pad = self['pad']
        borderWidth = self['borderWidth']
        self.bounds = [self.ll[0] - pad[0] - borderWidth[0],
         self.ur[0] + pad[0] + borderWidth[0],
         self.ll[2] - pad[1] - borderWidth[1],
         self.ur[2] + pad[1] + borderWidth[1]]
        return self.bounds
Beispiel #13
0
class IsisAgent(kinematicCharacterController, DirectObject):
    @classmethod
    def setPhysics(cls, physics):
        """ This method is set in src.loader when the generators are loaded
        into the namespace.  This frees the environment definitions (in 
        scenario files) from having to pass around the physics parameter 
        that is required for all IsisObjects """
        cls.physics = physics

    def __init__(self, name, queueSize=100):

        # load the model and the different animations for the model into an Actor object.
        self.actor = Actor(
            "media/models/boxman", {"walk": "media/models/boxman-walk", "idle": "media/models/boxman-idle"}
        )
        self.actor.setScale(1.0)
        self.actor.setH(0)
        # self.actor.setLODAnimation(10,5,2) # slows animation framerate when actor is far from camera, if you can figure out reasonable params
        self.actor.setColorScale(random.random(), random.random(), random.random(), 1.0)
        self.actorNodePath = NodePath("agent-%s" % name)
        self.activeModel = self.actorNodePath

        self.actorNodePath.reparentTo(render)

        self.actor.reparentTo(self.actorNodePath)
        self.name = name
        self.isMoving = False

        # initialize ODE controller
        kinematicCharacterController.__init__(self, IsisAgent.physics, self.actorNodePath)
        self.setGeomPos(self.actorNodePath.getPos(render))
        """
        Additional Direct Object that I use for convenience.
        """
        self.specialDirectObject = DirectObject()

        """
        How high above the center of the capsule you want the camera to be
        when walking and when crouching. It's related to the values in KCC.
        """
        self.walkCamH = 0.7
        self.crouchCamH = 0.2
        self.camH = self.walkCamH

        """
        This tells the Player Controller what we're aiming at.
        """
        self.aimed = None

        self.isSitting = False
        self.isDisabled = False

        """
        The special direct object is used for trigger messages and the like.
        """
        # self.specialDirectObject.accept("ladder_trigger_enter", self.setFly, [True])
        # self.specialDirectObject.accept("ladder_trigger_exit", self.setFly, [False])

        self.actor.makeSubpart("arms", ["LeftShoulder", "RightShoulder"])

        # Expose agent's right hand joint to attach objects to
        self.player_right_hand = self.actor.exposeJoint(None, "modelRoot", "Hand.R")
        self.player_left_hand = self.actor.exposeJoint(None, "modelRoot", "Hand.L")

        self.right_hand_holding_object = None
        self.left_hand_holding_object = None

        # don't change the color of things you pick up
        self.player_right_hand.setColorScaleOff()
        self.player_left_hand.setColorScaleOff()

        self.player_head = self.actor.exposeJoint(None, "modelRoot", "Head")
        self.neck = self.actor.controlJoint(None, "modelRoot", "Head")

        self.controlMap = {
            "turn_left": 0,
            "turn_right": 0,
            "move_forward": 0,
            "move_backward": 0,
            "move_right": 0,
            "move_left": 0,
            "look_up": 0,
            "look_down": 0,
            "look_left": 0,
            "look_right": 0,
            "jump": 0,
        }
        # see update method for uses, indices are [turn left, turn right, move_forward, move_back, move_right, move_left, look_up, look_down, look_right, look_left]
        # turns are in degrees per second, moves are in units per second
        self.speeds = [270, 270, 5, 5, 5, 5, 60, 60, 60, 60]

        self.originalPos = self.actor.getPos()

        bubble = loader.loadTexture("media/textures/thought_bubble.png")
        # bubble.setTransparency(TransparencyAttrib.MAlpha)

        self.speech_bubble = DirectLabel(
            parent=self.actor,
            text="",
            text_wordwrap=10,
            pad=(3, 3),
            relief=None,
            text_scale=(0.3, 0.3),
            pos=(0, 0, 3.6),
            frameColor=(0.6, 0.2, 0.1, 0.5),
            textMayChange=1,
            text_frame=(0, 0, 0, 1),
            text_bg=(1, 1, 1, 1),
        )
        # self.myImage=
        self.speech_bubble.setTransparency(TransparencyAttrib.MAlpha)
        # stop the speech bubble from being colored like the agent
        self.speech_bubble.setColorScaleOff()
        self.speech_bubble.component("text0").textNode.setCardDecal(1)
        self.speech_bubble.setBillboardAxis()
        # hide the speech bubble from IsisAgent's own camera
        self.speech_bubble.hide(BitMask32.bit(1))

        self.thought_bubble = DirectLabel(
            parent=self.actor,
            text="",
            text_wordwrap=9,
            text_frame=(1, 0, -2, 1),
            text_pos=(0, 0.5),
            text_bg=(1, 1, 1, 0),
            relief=None,
            frameSize=(0, 1.5, -2, 3),
            text_scale=(0.18, 0.18),
            pos=(0, 0.2, 3.6),
            textMayChange=1,
            image=bubble,
            image_pos=(0, 0.1, 0),
            sortOrder=5,
        )
        self.thought_bubble.setTransparency(TransparencyAttrib.MAlpha)
        # stop the speech bubble from being colored like the agent
        self.thought_bubble.setColorScaleOff()
        self.thought_bubble.component("text0").textNode.setFrameColor(1, 1, 1, 0)
        self.thought_bubble.component("text0").textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)
        self.thought_bubble.component("text0").textNode.setCardDecal(1)
        self.thought_bubble.setBillboardAxis()
        # hide the thought bubble from IsisAgent's own camera
        self.thought_bubble.hide(BitMask32.bit(1))
        # disable by default
        self.thought_bubble.hide()
        self.thought_filter = {}  # only show thoughts whose values are in here
        self.last_spoke = 0  # timers to keep track of last thought/speech and
        self.last_thought = 0  # hide visualizations

        # put a camera on ralph
        self.fov = NodePath(Camera("RaphViz"))
        self.fov.node().setCameraMask(BitMask32.bit(1))

        # position the camera to be infront of Boxman's face.
        self.fov.reparentTo(self.player_head)
        # x,y,z are not in standard orientation when parented to player-Head
        self.fov.setPos(0, 0.2, 0)
        # if P=0, canrea is looking directly up. 90 is back of head. -90 is on face.
        self.fov.setHpr(0, -90, 0)

        lens = self.fov.node().getLens()
        lens.setFov(60)  #  degree field of view (expanded from 40)
        lens.setNear(0.2)
        # self.fov.node().showFrustum() # displays a box around his head
        # self.fov.place()

        self.prevtime = 0
        self.current_frame_count = 0

        self.isSitting = False
        self.isDisabled = False
        self.msg = None
        self.actorNodePath.setPythonTag("agent", self)

        # Initialize the action queue, with a maximum length of queueSize
        self.queue = []
        self.queueSize = queueSize
        self.lastSense = 0

    def setLayout(self, layout):
        """ Dummy method called by spatial methods for use with objects. 
        Doesn't make sense for an agent that can move around."""
        pass

    def setPos(self, pos):
        """ Wrapper to set the position of the ODE geometry, which in turn 
        sets the visual model's geometry the next time the update() method
        is called. """
        self.setGeomPos(pos)

    def setPosition(self, pos):
        self.setPos(pos)

    def reparentTo(self, parent):
        self.actorNodePath.reparentTo(parent)

    def setControl(self, control, value):
        """Set the state of one of the character's movement controls.  """
        self.controlMap[control] = value

    def get_objects_in_field_of_vision(self, exclude=["isisobject"]):
        """ This works in an x-ray style. Fast. Works best if you listen to
        http://en.wikipedia.org/wiki/Rock_Art_and_the_X-Ray_Style while
        you use it.
        
        needs to exclude isisobjects since they cannot be serialized  
        """
        objects = {}
        for obj in base.render.findAllMatches("**/IsisObject*"):
            if not obj.hasPythonTag("isisobj"):
                continue
            o = obj.getPythonTag("isisobj")
            bounds = o.activeModel.getBounds()
            bounds.xform(o.activeModel.getMat(self.fov))
            if self.fov.node().isInView(o.activeModel.getPos(self.fov)):
                pos = o.activeModel.getPos(render)
                pos = (pos[0], pos[1], pos[2] + o.getHeight() / 2)
                p1 = self.fov.getRelativePoint(render, pos)
                p2 = Point2()
                self.fov.node().getLens().project(p1, p2)
                p3 = aspect2d.getRelativePoint(render2d, Point3(p2[0], 0, p2[1]))
                object_dict = {}
                if "x_pos" not in exclude:
                    object_dict["x_pos"] = p3[0]
                if "y_pos" not in exclude:
                    object_dict["y_pos"] = p3[2]
                if "distance" not in exclude:
                    object_dict["distance"] = o.activeModel.getDistance(self.fov)
                if "orientation" not in exclude:
                    object_dict["orientation"] = o.activeModel.getH(self.fov)
                if "actions" not in exclude:
                    object_dict["actions"] = o.list_actions()
                if "isisobject" not in exclude:
                    object_dict["isisobject"] = o
                # add item to dinctionary
                objects[o] = object_dict
        return objects

    def get_agents_in_field_of_vision(self):
        """ This works in an x-ray vision style as well"""
        agents = {}
        for agent in base.render.findAllMatches("**/agent-*"):
            if not agent.hasPythonTag("agent"):
                continue
            a = agent.getPythonTag("agent")
            bounds = a.actorNodePath.getBounds()
            bounds.xform(a.actorNodePath.getMat(self.fov))
            pos = a.actorNodePath.getPos(self.fov)
            if self.fov.node().isInView(pos):
                p1 = self.fov.getRelativePoint(render, pos)
                p2 = Point2()
                self.fov.node().getLens().project(p1, p2)
                p3 = aspect2d.getRelativePoint(render2d, Point3(p2[0], 0, p2[1]))
                agentDict = {
                    "x_pos": p3[0],
                    "y_pos": p3[2],
                    "distance": a.actorNodePath.getDistance(self.fov),
                    "orientation": a.actorNodePath.getH(self.fov),
                }
                agents[a] = agentDict
        return agents

    def in_view(self, isisobj):
        """ Returns true iff a particular isisobject is in view """
        return len(
            filter(lambda x: x["isisobject"] == isisobj, self.get_objects_in_field_of_vision(exclude=[]).values())
        )

    def get_objects_in_view(self):
        """ Gets objects through ray tracing.  Slow"""
        return self.picker.get_objects_in_view()

    def control__turn_left__start(self, speed=None):
        self.setControl("turn_left", 1)
        self.setControl("turn_right", 0)
        if speed:
            self.speeds[0] = speed
        return "success"

    def control__turn_left__stop(self):
        self.setControl("turn_left", 0)
        return "success"

    def control__turn_right__start(self, speed=None):
        self.setControl("turn_left", 0)
        self.setControl("turn_right", 1)
        if speed:
            self.speeds[1] = speed
        return "success"

    def control__turn_right__stop(self):
        self.setControl("turn_right", 0)
        return "success"

    def control__move_forward__start(self, speed=None):
        self.setControl("move_forward", 1)
        self.setControl("move_backward", 0)
        if speed:
            self.speeds[2] = speed
        return "success"

    def control__move_forward__stop(self):
        self.setControl("move_forward", 0)
        return "success"

    def control__move_backward__start(self, speed=None):
        self.setControl("move_forward", 0)
        self.setControl("move_backward", 1)
        if speed:
            self.speeds[3] = speed
        return "success"

    def control__move_backward__stop(self):
        self.setControl("move_backward", 0)
        return "success"

    def control__move_left__start(self, speed=None):
        self.setControl("move_left", 1)
        self.setControl("move_right", 0)
        if speed:
            self.speeds[4] = speed
        return "success"

    def control__move_left__stop(self):
        self.setControl("move_left", 0)
        return "success"

    def control__move_right__start(self, speed=None):
        self.setControl("move_right", 1)
        self.setControl("move_left", 0)
        if speed:
            self.speeds[5] = speed
        return "success"

    def control__move_right__stop(self):
        self.setControl("move_right", 0)
        return "success"

    def control__look_left__start(self, speed=None):
        self.setControl("look_left", 1)
        self.setControl("look_right", 0)
        if speed:
            self.speeds[9] = speed
        return "success"

    def control__look_left__stop(self):
        self.setControl("look_left", 0)
        return "success"

    def control__look_right__start(self, speed=None):
        self.setControl("look_right", 1)
        self.setControl("look_left", 0)
        if speed:
            self.speeds[8] = speed
        return "success"

    def control__look_right__stop(self):
        self.setControl("look_right", 0)
        return "success"

    def control__look_up__start(self, speed=None):
        self.setControl("look_up", 1)
        self.setControl("look_down", 0)
        if speed:
            self.speeds[6] = speed
        return "success"

    def control__look_up__stop(self):
        self.setControl("look_up", 0)
        return "success"

    def control__look_down__start(self, speed=None):
        self.setControl("look_down", 1)
        self.setControl("look_up", 0)
        if speed:
            self.speeds[7] = speed
        return "success"

    def control__look_down__stop(self):
        self.setControl("look_down", 0)
        return "success"

    def control__jump(self):
        self.setControl("jump", 1)
        return "success"

    def control__view_objects(self):
        """ calls a raytrace to to all objects in view """
        objects = self.get_objects_in_field_of_vision()
        self.control__say("If I were wearing x-ray glasses, I could see %i items" % len(objects))
        print "Objects in view:", objects
        return objects

    def control__sense(self):
        """ perceives the world, returns percepts dict """
        percepts = dict()
        # eyes: visual matricies
        # percepts['vision'] = self.sense__get_vision()
        # objects in purview (cheating object recognition)
        percepts["objects"] = self.sense__get_objects()
        # global position in environment - our robots can have GPS :)
        percepts["position"] = self.sense__get_position()
        # language: get last utterances that were typed
        percepts["language"] = self.sense__get_utterances()
        # agents: returns a map of agents to a list of actions that have been sensed
        percepts["agents"] = self.sense__get_agents()
        print percepts
        return percepts

    def control__think(self, message, layer=0):
        """ Changes the contents of an agent's thought bubble"""
        # only say things that are checked in the controller
        if self.thought_filter.has_key(layer):
            self.thought_bubble.show()
            self.thought_bubble["text"] = message
            # self.thought_bubble.component('text0').textNode.setShadow(0.05, 0.05)
            # self.thought_bubble.component('text0').textNode.setShadowColor(self.thought_filter[layer])
            self.last_thought = 0
        return "success"

    def control__say(self, message="Hello!"):
        self.speech_bubble["text"] = message
        self.last_spoke = 0
        return "success"

    """

    Methods explicitly for IsisScenario files 

    """

    def put_in_front_of(self, isisobj):
        # find open direction
        pos = isisobj.getGeomPos()
        direction = render.getRelativeVector(isisobj, Vec3(0, 1.0, 0))
        closestEntry, closestObject = IsisAgent.physics.doRaycastNew("aimRay", 5, [pos, direction], [isisobj.geom])
        print "CLOSEST", closestEntry, closestObject
        if closestObject == None:
            self.setPosition(pos + Vec3(0, 2, 0))
        else:
            print "CANNOT PLACE IN FRONT OF %s BECAUSE %s IS THERE" % (isisobj, closestObject)
            direction = render.getRelativeVector(isisobj, Vec3(0, -1.0, 0))
            closestEntry, closestObject = IsisAgent.physics.doRaycastNew("aimRay", 5, [pos, direction], [isisobj.geom])
            if closestEntry == None:
                self.setPosition(pos + Vec3(0, -2, 0))
            else:
                print "CANNOT PLACE BEHIND %s BECAUSE %s IS THERE" % (isisobj, closestObject)
                direction = render.getRelativeVector(isisobj, Vec3(1, 0, 0))
                closestEntry, closestObject = IsisAgent.physics.doRaycastNew(
                    "aimRay", 5, [pos, direction], [isisobj.geom]
                )
                if closestEntry == None:
                    self.setPosition(pos + Vec3(2, 0, 0))
                else:
                    print "CANNOT PLACE TO LEFT OF %s BECAUSE %s IS THERE" % (isisobj, closestObject)
                    # there's only one option left, do it anyway
                    self.setPosition(pos + Vec3(-2, 0, 0))
        # rotate agent to look at it
        self.actorNodePath.setPos(self.getGeomPos())
        self.actorNodePath.lookAt(pos)
        self.setH(self.actorNodePath.getH())

    def put_in_right_hand(self, target):
        return self.pick_object_up_with(target, self.right_hand_holding_object, self.player_right_hand)

    def put_in_left_hand(self, target):
        return self.pick_object_up_with(target, self.left_hand_holding_object, self.player_left_hand)

    def __get_object_in_center_of_view(self):
        direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
        pos = self.fov.getPos(render)
        exclude = []  # [base.render.find("**/kitchenNode*").getPythonTag("isisobj").geom]
        closestEntry, closestObject = IsisAgent.physics.doRaycastNew("aimRay", 5, [pos, direction], exclude)
        return closestObject

    def pick_object_up_with(self, target, hand_slot, hand_joint):
        """ Attaches an IsisObject, target, to the hand joint.  Does not check anything first,
        other than the fact that the hand joint is not currently holding something else."""
        if hand_slot != None:
            print "already holding " + hand_slot.getName() + "."
            return None
        else:
            if target.layout:
                target.layout.remove(target)
                target.layout = None
            # store original position
            target.originalHpr = target.getHpr(render)
            target.disable()  # turn off physics
            if target.body:
                target.body.setGravityMode(0)
            target.reparentTo(hand_joint)
            target.setPosition(hand_joint.getPos(render))
            target.setTag("heldBy", self.name)
            if hand_joint == self.player_right_hand:
                self.right_hand_holding_object = target
            elif hand_joint == self.player_left_hand:
                self.left_hand_holding_object = target
            hand_slot = target
            return target

    def control__pick_up_with_right_hand(self, target=None):
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return "error: no target in reach"
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        print "attempting to pick up " + target.name + " with right hand.\n"
        if self.can_grasp(target):  # object within distance
            return self.pick_object_up_with(target, self.right_hand_holding_object, self.player_right_hand)
        else:
            print "object (" + target.name + ") is not graspable (i.e. in view and close enough)."
            return "error: object not graspable"

    def control__pick_up_with_left_hand(self, target=None):
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        print "attempting to pick up " + target.name + " with left hand.\n"
        if self.can_grasp(target):  # object within distance
            return self.pick_object_up_with(target, self.left_hand_holding_object, self.player_left_hand)
        else:
            print "object (" + target.name + ") is not graspable (i.e. in view and close enough)."
            return "error: object not graspable"

    def control__drop_from_right_hand(self):
        print "attempting to drop object from right hand.\n"

        if self.right_hand_holding_object is None:
            print "right hand is not holding an object."
            return False
        if self.right_hand_holding_object.getNetTag("heldBy") == self.name:
            self.right_hand_holding_object.reparentTo(render)
            direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
            pos = self.player_right_hand.getPos(render)
            heldPos = self.right_hand_holding_object.geom.getPosition()
            self.right_hand_holding_object.setPosition(pos)
            self.right_hand_holding_object.synchPosQuatToNode()
            self.right_hand_holding_object.setTag("heldBy", "")
            self.right_hand_holding_object.setRotation(self.right_hand_holding_object.originalHpr)
            self.right_hand_holding_object.enable()
            if self.right_hand_holding_object.body:
                quat = self.getQuat()
                # throw object
                force = 5
                self.right_hand_holding_object.body.setGravityMode(1)
                self.right_hand_holding_object.getBody().setForce(quat.xform(Vec3(0, force, 0)))
            self.right_hand_holding_object = None
            return "success"
        else:
            return "Error: not being held by agent %s" % (self.name)

    def control__drop_from_left_hand(self):
        print "attempting to drop object from left hand.\n"
        if self.left_hand_holding_object is None:
            return "left hand is not holding an object."
        if self.left_hand_holding_object.getNetTag("heldBy") == self.name:
            self.left_hand_holding_object.reparentTo(render)
            direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
            pos = self.player_left_hand.getPos(render)
            heldPos = self.left_hand_holding_object.geom.getPosition()
            self.left_hand_holding_object.setPosition(pos)
            self.left_hand_holding_object.synchPosQuatToNode()
            self.left_hand_holding_object.setTag("heldBy", "")
            self.left_hand_holding_object.setRotation(self.left_hand_holding_object.originalHpr)
            self.left_hand_holding_object.enable()
            if self.left_hand_holding_object.body:
                quat = self.getQuat()
                # throw object
                force = 5
                self.left_hand_holding_object.body.setGravityMode(1)
                self.left_hand_holding_object.getBody().setForce(quat.xform(Vec3(0, force, 0)))
            self.left_hand_holding_object = None
            return "success"
        else:
            return "Error: not being held by agent %s" % (self.name)

    def control__use_right_hand(self, target=None, action=None):
        # TODO, rename this to use object with
        if not action:
            if self.msg:
                action = self.msg
            else:
                action = "divide"
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        print "Trying to use object", target
        if self.can_grasp(target):
            if target.call(self, action, self.right_hand_holding_object) or (
                self.right_hand_holding_object and self.right_hand_holding_object.call(self, action, target)
            ):
                return "success"
            return str(action) + " not associated with either target or object"
        return "target not within reach"

    def control__use_left_hand(self, target=None, action=None):
        if not action:
            if self.msg:
                action = self.msg
            else:
                action = "divide"
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        if self.can_grasp(target):
            if target.call(self, action, self.left_hand_holding_object) or (
                self.left_hand_holding_object and self.left_hand_holding_object.call(self, action, target)
            ):
                return "success"
            return str(action) + " not associated with either target or object"
        return "target not within reach"

    def can_grasp(self, isisobject):
        distance = isisobject.activeModel.getDistance(self.fov)
        print "distance = ", distance
        return distance < 5.0

    def is_holding(self, object_name):
        return (
            self.left_hand_holding_object
            and (self.left_hand_holding_object.getPythonTag("isisobj").name == object_name)
        ) or (
            self.right_hand_holding_object
            and (self.right_hand_holding_object.getPythonTag("isisobj").name == object_name)
        )

    def empty_hand(self):
        if self.left_hand_holding_object is None:
            return self.player_left_hand
        elif self.right_hand_holding_object is None:
            return self.player_right_hand
        return False

    def has_empty_hand(self):
        return self.empty_hand() is not False

    def control__use_aimed(self):
        """
        Try to use the object that we aim at, by calling its callback method.
        """
        target = self.__get_object_in_center_of_view()
        if target.selectionCallback:
            target.selectionCallback(self, dir)
        return "success"

    def sense__get_position(self):
        x, y, z = self.actorNodePath.getPos()
        h, p, r = self.actorNodePath.getHpr()
        # FIXME
        # neck is not positioned in Blockman nh,np,nr = self.agents[agent_id].actor_neck.getHpr()
        left_hand_obj = ""
        right_hand_obj = ""
        if self.left_hand_holding_object:
            left_hand_obj = self.left_hand_holding_object.getName()
        if self.right_hand_holding_object:
            right_hand_obj = self.right_hand_holding_object.getName()
        return {
            "body_x": x,
            "body_y": y,
            "body_z": z,
            "body_h": h,
            "body_p": p,
            "body_r": r,
            "in_left_hand": left_hand_obj,
            "in_right_hand": right_hand_obj,
        }

    def sense__get_vision(self):
        self.fov.node().saveScreenshot("temp.jpg")
        image = Image.open("temp.jpg")
        os.remove("temp.jpg")
        return image

    def sense__get_objects(self):
        return dict([x.getName(), y] for (x, y) in self.get_objects_in_field_of_vision().items())

    def sense__get_agents(self):
        curSense = time()
        agents = {}
        for k, v in self.get_agents_in_field_of_vision().items():
            v["actions"] = k.get_other_agents_actions(self.lastSense, curSense)
            agents[k.name] = v
        self.lastSense = curSense
        return agents

    def sense__get_utterances(self):
        """ Clear out the buffer of things that the teacher has typed,
        FIXME: this doesn't work right now """
        return []
        utterances = self.teacher_utterances
        self.teacher_utterances = []
        return utterances

    def debug__print_objects(self):
        text = "Objects in FOV: " + ", ".join(self.sense__get_objects().keys())
        print text

    def add_action_to_history(self, action, args, result=0):
        self.queue.append((time(), action, args, result))
        if len(self.queue) > self.queueSize:
            self.queue.pop(0)

    def get_other_agents_actions(self, start=0, end=None):
        if not end:
            end = time()
        actions = []
        for act in self.queue:
            if act[0] >= start:
                if act[0] < end:
                    actions.append(act)
                else:
                    break
        return actions

    def update(self, stepSize=0.1):
        self.speed = [0.0, 0.0]
        self.actorNodePath.setPos(self.geom.getPosition() + Vec3(0, 0, -0.70))
        self.actorNodePath.setQuat(self.getQuat())
        # the values in self.speeds are used as coefficientes for turns and movements
        if self.controlMap["turn_left"] != 0:
            self.addToH(stepSize * self.speeds[0])
        if self.controlMap["turn_right"] != 0:
            self.addToH(-stepSize * self.speeds[1])
        if self.verticalState == "ground":
            # these actions require contact with the ground
            if self.controlMap["move_forward"] != 0:
                self.speed[1] = self.speeds[2]
            if self.controlMap["move_backward"] != 0:
                self.speed[1] = -self.speeds[3]
            if self.controlMap["move_left"] != 0:
                self.speed[0] = -self.speeds[4]
            if self.controlMap["move_right"] != 0:
                self.speed[0] = self.speeds[5]
            if self.controlMap["jump"] != 0:
                kinematicCharacterController.jump(self)
                # one jump at a time!
                self.controlMap["jump"] = 0
        if self.controlMap["look_left"] != 0:
            self.neck.setR(bound(self.neck.getR(), -60, 60) + stepSize * 80)
        if self.controlMap["look_right"] != 0:
            self.neck.setR(bound(self.neck.getR(), -60, 60) - stepSize * 80)
        if self.controlMap["look_up"] != 0:
            self.neck.setP(bound(self.neck.getP(), -60, 80) + stepSize * 80)
        if self.controlMap["look_down"] != 0:
            self.neck.setP(bound(self.neck.getP(), -60, 80) - stepSize * 80)

        kinematicCharacterController.update(self, stepSize)

        """
        Update the held object position to be in the hands
        """
        if self.right_hand_holding_object != None:
            self.right_hand_holding_object.setPosition(self.player_right_hand.getPos(render))
        if self.left_hand_holding_object != None:
            self.left_hand_holding_object.setPosition(self.player_left_hand.getPos(render))

        # Update the dialog box and thought windows
        # This allows dialogue window to gradually decay (changing transparancy) and then disappear
        self.last_spoke += stepSize / 2
        self.last_thought += stepSize / 2
        self.speech_bubble["text_bg"] = (1, 1, 1, 1 / (self.last_spoke + 0.01))
        self.speech_bubble["frameColor"] = (0.6, 0.2, 0.1, 0.5 / (self.last_spoke + 0.01))
        if self.last_spoke > 2:
            self.speech_bubble["text"] = ""
        if self.last_thought > 1:
            self.thought_bubble.hide()

        # If the character is moving, loop the run animation.
        # If he is standing still, stop the animation.
        if (
            (self.controlMap["move_forward"] != 0)
            or (self.controlMap["move_backward"] != 0)
            or (self.controlMap["move_left"] != 0)
            or (self.controlMap["move_right"] != 0)
        ):
            if self.isMoving is False:
                self.isMoving = True
        else:
            if self.isMoving:
                self.current_frame_count = 5.0
                self.isMoving = False

        total_frame_num = self.actor.getNumFrames("walk")
        if self.isMoving:
            self.current_frame_count = self.current_frame_count + (stepSize * 250.0)
            if self.current_frame_count > total_frame_num:
                self.current_frame_count = self.current_frame_count % total_frame_num
            self.actor.pose("walk", self.current_frame_count)
        elif self.current_frame_count != 0:
            self.current_frame_count = 0
            self.actor.pose("idle", 0)
        return Task.cont

    def destroy(self):
        self.disable()
        self.specialDirectObject.ignoreAll()
        self.actorNodePath.removeNode()
        del self.specialDirectObject

        kinematicCharacterController.destroy(self)

    def disable(self):
        self.isDisabled = True
        self.geom.disable()
        self.footRay.disable()

    def enable(self):
        self.footRay.enable()
        self.geom.enable()
        self.isDisabled = False

    """
    Set camera to correct height above the center of the capsule
    when crouching and when standing up.
    """

    def crouch(self):
        kinematicCharacterController.crouch(self)
        self.camH = self.crouchCamH

    def crouchStop(self):
        """
        Only change the camera's placement when the KCC allows standing up.
        See the KCC to find out why it might not allow it.
        """
        if kinematicCharacterController.crouchStop(self):
            self.camH = self.walkCamH
class PopupMouseWatcherRegion(MouseWatcherRegion):
    """
    This is an ultra hacky class!
    The correct implementation of PopupMouseWatcherRegion cannot be done in Python.
    This also assumes that m_mouse_watcher is NametagGlobals::_mouse_watcher.
    """

    class _Param:
        def __init__(self, outside=False):
            self.outside = outside

        def isOutside(self):
            return self.outside

        def getButton(self):
            return MouseButton.one()

    MOUSE_WATCHER_SETUP = False

    def __init__(self, obj, name, frame):
        MouseWatcherRegion.__init__(self, '%s-%s' % (name, id(self)), frame)

        self.obj = obj
        self.__inside = False
        self.__active = False

        if not self.MOUSE_WATCHER_SETUP:
            NametagGlobals._mouse_watcher.setEnterPattern('mouse-enter-%r')
            NametagGlobals._mouse_watcher.setLeavePattern('mouse-leave-%r')
            NametagGlobals._mouse_watcher.setButtonDownPattern('button-down-%r')
            NametagGlobals._mouse_watcher.setButtonUpPattern('button-up-%r')
            self.MOUSE_WATCHER_SETUP = True

        self.slaveObject = DirectObject()
        self.activate()

    def activate(self):
        if not self.__active:
            self.__active = True

            self.slaveObject.accept(self.__getEvent(NametagGlobals._mouse_watcher.getEnterPattern()), self.__mouseEnter)
            self.slaveObject.accept(self.__getEvent(NametagGlobals._mouse_watcher.getLeavePattern()), self.__mouseLeave)
            self.slaveObject.accept(self.__getEvent(NametagGlobals._mouse_watcher.getButtonDownPattern()),
                                    self.__buttonDown)
            self.slaveObject.accept(self.__getEvent(NametagGlobals._mouse_watcher.getButtonUpPattern()), self.__buttonUp)

    def deactivate(self):
        if self.__active:
            self.__active = False

            self.slaveObject.ignoreAll()

    def __mouseEnter(self, region, extra):
        self.__inside = True
        self.obj.enterRegion(None)

    def __mouseLeave(self, region, extra):
        self.__inside = False
        self.obj.exitRegion(None)

    def __buttonDown(self, region, button):
        if button == 'mouse1':
            self.obj.press(PopupMouseWatcherRegion._Param())

    def __buttonUp(self, region, button):
        if button == 'mouse1':
            self.obj.release(PopupMouseWatcherRegion._Param(not self.__inside))

    def __getEvent(self, pattern):
        return pattern.replace('%r', self.getName())
class KeyboardMapping(SettingsObject):
    '''
    Stores the user's configured keyboard map.
    '''

    dataFileName = 'keys'
    attributes = (
        ('actions', 'actions', None),
    )

    def __init__(self, app):
        self.do = DirectObject()
        super(KeyboardMapping, self).__init__(app)

    def reset(self):
        super(KeyboardMapping, self).reset()

        if self.actions is None:
            self.revertToDefault()
        self.installMapping()

    def revertToDefault(self):
        self.actions = {}

        # Load the old file if it exists
        oldFilePath = getPath(user, 'keymap')
        try:
            with open(oldFilePath, 'rU') as f:
                lines = f.read().splitlines()
            for line in lines:
                bits = line.split(':', 1)
                if len(bits) == 2 and bits[0].isdigit():
                    key = pygameToPandaKey(int(bits[0]))
                    self.actions.setdefault(key, bits[1])
        except IOError:
            pass

        for key, action in self.getDefaultKeyMap():
            self.actions.setdefault(key, action)

    def getDefaultKeyMap(self):
        defaults = []

        # Default WASD in the current keyboard layout
        layout = self.app.panda.win.get_keyboard_map()
        defaults.append((str(layout.get_mapped_button('a')), ACTION_LEFT))
        defaults.append((str(layout.get_mapped_button('s')), ACTION_DOWN))
        defaults.append((str(layout.get_mapped_button('d')), ACTION_RIGHT))
        defaults.append((str(layout.get_mapped_button('w')), ACTION_JUMP))

        defaults.extend([
            # Used in replay mode.
            ('=', ACTION_FOLLOW),

            # Menu keys.
            ('escape', ACTION_MAIN_MENU),

            ('b', ACTION_UPGRADE_MENU),
            ('space', ACTION_USE_UPGRADE),
            ('f12', ACTION_EDIT_PLAYER_INFO),
            ('v', ACTION_MORE_MENU),
            ('r', ACTION_RESPAWN),
            ('t', ACTION_EMOTE),
            ('y', ACTION_READY),
            ('pause', ACTION_PAUSE_GAME),

            ('0', ACTION_CLEAR_UPGRADE),

            ('m', ACTION_ABANDON_UPGRADE),
            ('enter', ACTION_CHAT),
            ('p', ACTION_LEADERBOARD_TOGGLE),
            ('delete', ACTION_HUD_TOGGLE),

            ('scroll_lock', ACTION_TERMINAL_TOGGLE),
        ])

        for upgradeClass in allUpgrades:
            if upgradeClass.defaultKey is not None:
                defaults.append((upgradeClass.defaultKey, upgradeClass.action))

        return defaults

    def apply(self):
        self.installMapping()

    def installMapping(self):
        self.do.ignoreAll()     # Clear previous mapping
        for k, action in self.actions.items():
            self.do.accept(k, messenger.send, [keyDownEvent(action)])
            self.do.accept(k + '-up', messenger.send, [keyUpEvent(action)])
class DirectEntry(DirectFrame):
    """
    DirectEntry(parent) - Create a DirectGuiWidget which responds
    to keyboard buttons
    """

    directWtext = ConfigVariableBool('direct-wtext', 1)

    AllowCapNamePrefixes = (
        "Al",
        "Ap",
        "Ben",
        "De",
        "Del",
        "Della",
        "Delle",
        "Der",
        "Di",
        "Du",
        "El",
        "Fitz",
        "La",
        "Las",
        "Le",
        "Les",
        "Lo",
        "Los",
        "Mac",
        "St",
        "Te",
        "Ten",
        "Van",
        "Von",
    )
    ForceCapNamePrefixes = (
        "D'",
        "DeLa",
        "Dell'",
        "L'",
        "M'",
        "Mc",
        "O'",
    )

    def __init__(self, parent=None, **kw):
        # Inherits from DirectFrame
        # A Direct Frame can have:
        # - A background texture (pass in path to image, or Texture Card)
        # - A midground geometry item (pass in geometry)
        # - A foreground text Node (pass in text string or Onscreen Text)
        # For a direct entry:
        # Each button has 3 states (focus, noFocus, disabled)
        # The same image/geom/text can be used for all three states or each
        # state can have a different text/geom/image
        # State transitions happen automatically based upon mouse interaction
        optiondefs = (
            # Define type of DirectGuiWidget
            ('pgFunc', PGEntry, None),
            ('numStates', 3, None),
            ('state', DGG.NORMAL, None),
            ('entryFont', None, DGG.INITOPT),
            ('width', 10, self.setup),
            ('numLines', 1, self.setup),
            ('focus', 0, self.setFocus),
            ('cursorKeys', 1, self.setCursorKeysActive),
            ('obscured', 0, self.setObscureMode),
            # Setting backgroundFocus allows the entry box to get keyboard
            # events that are not handled by other things (i.e. events that
            # fall through to the background):
            ('backgroundFocus', 0, self.setBackgroundFocus),
            # Text used for the PGEntry text node
            # NOTE: This overrides the DirectFrame text option
            ('initialText', '', DGG.INITOPT),
            # Command to be called on hitting Enter
            ('command', None, None),
            ('extraArgs', [], None),
            # Command to be called when enter is hit but we fail to submit
            ('failedCommand', None, None),
            ('failedExtraArgs', [], None),
            # commands to be called when focus is gained or lost
            ('focusInCommand', None, None),
            ('focusInExtraArgs', [], None),
            ('focusOutCommand', None, None),
            ('focusOutExtraArgs', [], None),
            # Sounds to be used for button events
            ('rolloverSound', DGG.getDefaultRolloverSound(),
             self.setRolloverSound),
            ('clickSound', DGG.getDefaultClickSound(), self.setClickSound),
            ('autoCapitalize', 0, self.autoCapitalizeFunc),
            ('autoCapitalizeAllowPrefixes', DirectEntry.AllowCapNamePrefixes,
             None),
            ('autoCapitalizeForcePrefixes', DirectEntry.ForceCapNamePrefixes,
             None),
        )
        # Merge keyword options with default options
        self.defineoptions(kw, optiondefs)

        # Initialize superclasses
        DirectFrame.__init__(self, parent)

        if self['entryFont'] == None:
            font = DGG.getDefaultFont()
        else:
            font = self['entryFont']

        # Create Text Node Component
        self.onscreenText = self.createcomponent(
            'text',
            (),
            None,
            OnscreenText,
            (),
            parent=hidden,
            # Pass in empty text to avoid extra work, since its really
            # The PGEntry which will use the TextNode to generate geometry
            text='',
            align=TextNode.ALeft,
            font=font,
            scale=1,
            # Don't get rid of the text node
            mayChange=1)

        # We can get rid of the node path since we're just using the
        # onscreenText as an easy way to access a text node as a
        # component
        self.onscreenText.removeNode()

        # Bind command function
        self.bind(DGG.ACCEPT, self.commandFunc)
        self.bind(DGG.ACCEPTFAILED, self.failedCommandFunc)

        self.accept(self.guiItem.getFocusInEvent(), self.focusInCommandFunc)
        self.accept(self.guiItem.getFocusOutEvent(), self.focusOutCommandFunc)

        # listen for auto-capitalize events on a separate object to prevent
        # clashing with other parts of the system
        self._autoCapListener = DirectObject()

        # Call option initialization functions
        self.initialiseoptions(DirectEntry)

        if not hasattr(self, 'autoCapitalizeAllowPrefixes'):
            self.autoCapitalizeAllowPrefixes = DirectEntry.AllowCapNamePrefixes
        if not hasattr(self, 'autoCapitalizeForcePrefixes'):
            self.autoCapitalizeForcePrefixes = DirectEntry.ForceCapNamePrefixes

        # Update TextNodes for each state
        for i in range(self['numStates']):
            self.guiItem.setTextDef(i, self.onscreenText.textNode)

        # Now we should call setup() again to make sure it has the
        # right font def.
        self.setup()

        # Update initial text
        self.unicodeText = 0
        if self['initialText']:
            self.enterText(self['initialText'])

    def destroy(self):
        self.ignoreAll()
        self._autoCapListener.ignoreAll()
        DirectFrame.destroy(self)

    def setup(self):
        self.guiItem.setupMinimal(self['width'], self['numLines'])

    def setFocus(self):
        PGEntry.setFocus(self.guiItem, self['focus'])

    def setCursorKeysActive(self):
        PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])

    def setObscureMode(self):
        PGEntry.setObscureMode(self.guiItem, self['obscured'])

    def setBackgroundFocus(self):
        PGEntry.setBackgroundFocus(self.guiItem, self['backgroundFocus'])

    def setRolloverSound(self):
        rolloverSound = self['rolloverSound']
        if rolloverSound:
            self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound)
        else:
            self.guiItem.clearSound(DGG.ENTER + self.guiId)

    def setClickSound(self):
        clickSound = self['clickSound']
        if clickSound:
            self.guiItem.setSound(DGG.ACCEPT + self.guiId, clickSound)
        else:
            self.guiItem.clearSound(DGG.ACCEPT + self.guiId)

    def commandFunc(self, event):
        if self['command']:
            # Pass any extra args to command
            apply(self['command'], [self.get()] + self['extraArgs'])

    def failedCommandFunc(self, event):
        if self['failedCommand']:
            # Pass any extra args
            apply(self['failedCommand'],
                  [self.get()] + self['failedExtraArgs'])

    def autoCapitalizeFunc(self):
        if self['autoCapitalize']:
            self._autoCapListener.accept(self.guiItem.getTypeEvent(),
                                         self._handleTyping)
            self._autoCapListener.accept(self.guiItem.getEraseEvent(),
                                         self._handleErasing)
        else:
            self._autoCapListener.ignore(self.guiItem.getTypeEvent())
            self._autoCapListener.ignore(self.guiItem.getEraseEvent())

    def focusInCommandFunc(self):
        if self['focusInCommand']:
            apply(self['focusInCommand'], self['focusInExtraArgs'])
        if self['autoCapitalize']:
            self.accept(self.guiItem.getTypeEvent(), self._handleTyping)
            self.accept(self.guiItem.getEraseEvent(), self._handleErasing)

    def _handleTyping(self, guiEvent):
        self._autoCapitalize()

    def _handleErasing(self, guiEvent):
        self._autoCapitalize()

    def _autoCapitalize(self):
        name = self.get().decode('utf-8')
        # capitalize each word, allowing for things like McMutton
        capName = ''
        # track each individual word to detect prefixes like Mc
        wordSoFar = ''
        # track whether the previous character was part of a word or not
        wasNonWordChar = True
        for i in xrange(len(name)):
            character = name[i]
            # test to see if we are between words
            # - Count characters that can't be capitalized as a break between words
            #   This assumes that string.lower and string.upper will return different
            #   values for all unicode letters.
            # - Don't count apostrophes as a break between words
            if ((string.lower(character) == string.upper(character))
                    and (character != "'")):
                # we are between words
                wordSoFar = ''
                wasNonWordChar = True
            else:
                capitalize = False
                if wasNonWordChar:
                    # first letter of a word, capitalize it unconditionally;
                    capitalize = True
                elif (character == string.upper(character)
                      and len(self.autoCapitalizeAllowPrefixes)
                      and wordSoFar in self.autoCapitalizeAllowPrefixes):
                    # first letter after one of the prefixes, allow it to be capitalized
                    capitalize = True
                elif (len(self.autoCapitalizeForcePrefixes)
                      and wordSoFar in self.autoCapitalizeForcePrefixes):
                    # first letter after one of the force prefixes, force it to be capitalized
                    capitalize = True
                if capitalize:
                    # allow this letter to remain capitalized
                    character = string.upper(character)
                else:
                    character = string.lower(character)
                wordSoFar += character
                wasNonWordChar = False
            capName += character
        self.enterText(capName.encode('utf-8'))

    def focusOutCommandFunc(self):
        if self['focusOutCommand']:
            apply(self['focusOutCommand'], self['focusOutExtraArgs'])
        if self['autoCapitalize']:
            self.ignore(self.guiItem.getTypeEvent())
            self.ignore(self.guiItem.getEraseEvent())

    def set(self, text):
        """ Changes the text currently showing in the typable region;
        does not change the current cursor position.  Also see
        enterText(). """

        self.unicodeText = isinstance(text, types.UnicodeType)
        if self.unicodeText:
            self.guiItem.setWtext(text)
        else:
            self.guiItem.setText(text)

    def get(self, plain=False):
        """ Returns the text currently showing in the typable region.
        If plain is True, the returned text will not include any
        formatting characters like nested color-change codes. """

        wantWide = self.unicodeText or self.guiItem.isWtext()
        if not self.directWtext.getValue():
            # If the user has configured wide-text off, then always
            # return an 8-bit string.  This will be encoded if
            # necessary, according to Panda's default encoding.
            wantWide = False

        if plain:
            if wantWide:
                return self.guiItem.getPlainWtext()
            else:
                return self.guiItem.getPlainText()
        else:
            if wantWide:
                return self.guiItem.getWtext()
            else:
                return self.guiItem.getText()

    def setCursorPosition(self, pos):
        if (pos < 0):
            self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() +
                                           pos)
        else:
            self.guiItem.setCursorPosition(pos)

    def enterText(self, text):
        """ sets the entry's text, and moves the cursor to the end """
        self.set(text)
        self.setCursorPosition(self.guiItem.getNumCharacters())

    def getFont(self):
        return self.onscreenText.getFont()

    def getBounds(self, state=0):
        # Compute the width and height for the entry itself, ignoring
        # geometry etc.
        tn = self.onscreenText.textNode
        mat = tn.getTransform()
        align = tn.getAlign()
        lineHeight = tn.getLineHeight()
        numLines = self['numLines']
        width = self['width']

        if align == TextNode.ALeft:
            left = 0.0
            right = width
        elif align == TextNode.ACenter:
            left = -width / 2.0
            right = width / 2.0
        elif align == TextNode.ARight:
            left = -width
            right = 0.0

        bottom = -0.3 * lineHeight - (lineHeight * (numLines - 1))
        top = lineHeight

        self.ll.set(left, 0.0, bottom)
        self.ur.set(right, 0.0, top)
        self.ll = mat.xformPoint(Point3.rfu(left, 0.0, bottom))
        self.ur = mat.xformPoint(Point3.rfu(right, 0.0, top))

        vec_right = Vec3.right()
        vec_up = Vec3.up()
        left = (vec_right[0] * self.ll[0] + vec_right[1] * self.ll[1] +
                vec_right[2] * self.ll[2])
        right = (vec_right[0] * self.ur[0] + vec_right[1] * self.ur[1] +
                 vec_right[2] * self.ur[2])
        bottom = (vec_up[0] * self.ll[0] + vec_up[1] * self.ll[1] +
                  vec_up[2] * self.ll[2])
        top = (vec_up[0] * self.ur[0] + vec_up[1] * self.ur[1] +
               vec_up[2] * self.ur[2])
        self.ll = Point3(left, 0.0, bottom)
        self.ur = Point3(right, 0.0, top)

        # Scale bounds to give a pad around graphics.  We also want to
        # scale around the border width.
        pad = self['pad']
        borderWidth = self['borderWidth']
        self.bounds = [
            self.ll[0] - pad[0] - borderWidth[0],
            self.ur[0] + pad[0] + borderWidth[0],
            self.ll[2] - pad[1] - borderWidth[1],
            self.ur[2] + pad[1] + borderWidth[1]
        ]
        return self.bounds
Beispiel #17
0
class IsisAgent(kinematicCharacterController, DirectObject):
    @classmethod
    def setPhysics(cls, physics):
        """ This method is set in src.loader when the generators are loaded
        into the namespace.  This frees the environment definitions (in 
        scenario files) from having to pass around the physics parameter 
        that is required for all IsisObjects """
        cls.physics = physics

    def __init__(self, name, queueSize=100):

        # load the model and the different animations for the model into an Actor object.
        self.actor = Actor("media/models/boxman", {
            "walk": "media/models/boxman-walk",
            "idle": "media/models/boxman-idle"
        })
        self.actor.setScale(1.0)
        self.actor.setH(0)
        #self.actor.setLODAnimation(10,5,2) # slows animation framerate when actor is far from camera, if you can figure out reasonable params
        self.actor.setColorScale(random.random(), random.random(),
                                 random.random(), 1.0)
        self.actorNodePath = NodePath('agent-%s' % name)
        self.activeModel = self.actorNodePath

        self.actorNodePath.reparentTo(render)

        self.actor.reparentTo(self.actorNodePath)
        self.name = name
        self.isMoving = False

        # initialize ODE controller
        kinematicCharacterController.__init__(self, IsisAgent.physics,
                                              self.actorNodePath)
        self.setGeomPos(self.actorNodePath.getPos(render))
        """
        Additional Direct Object that I use for convenience.
        """
        self.specialDirectObject = DirectObject()
        """
        How high above the center of the capsule you want the camera to be
        when walking and when crouching. It's related to the values in KCC.
        """
        self.walkCamH = 0.7
        self.crouchCamH = 0.2
        self.camH = self.walkCamH
        """
        This tells the Player Controller what we're aiming at.
        """
        self.aimed = None

        self.isSitting = False
        self.isDisabled = False
        """
        The special direct object is used for trigger messages and the like.
        """
        #self.specialDirectObject.accept("ladder_trigger_enter", self.setFly, [True])
        #self.specialDirectObject.accept("ladder_trigger_exit", self.setFly, [False])

        self.actor.makeSubpart("arms", ["LeftShoulder", "RightShoulder"])

        # Expose agent's right hand joint to attach objects to
        self.player_right_hand = self.actor.exposeJoint(
            None, 'modelRoot', 'Hand.R')
        self.player_left_hand = self.actor.exposeJoint(None, 'modelRoot',
                                                       'Hand.L')

        self.right_hand_holding_object = None
        self.left_hand_holding_object = None

        # don't change the color of things you pick up
        self.player_right_hand.setColorScaleOff()
        self.player_left_hand.setColorScaleOff()

        self.player_head = self.actor.exposeJoint(None, 'modelRoot', 'Head')
        self.neck = self.actor.controlJoint(None, 'modelRoot', 'Head')

        self.controlMap = {
            "turn_left": 0,
            "turn_right": 0,
            "move_forward": 0,
            "move_backward": 0,
            "move_right": 0,
            "move_left": 0,
            "look_up": 0,
            "look_down": 0,
            "look_left": 0,
            "look_right": 0,
            "jump": 0
        }
        # see update method for uses, indices are [turn left, turn right, move_forward, move_back, move_right, move_left, look_up, look_down, look_right, look_left]
        # turns are in degrees per second, moves are in units per second
        self.speeds = [270, 270, 5, 5, 5, 5, 60, 60, 60, 60]

        self.originalPos = self.actor.getPos()

        bubble = loader.loadTexture("media/textures/thought_bubble.png")
        #bubble.setTransparency(TransparencyAttrib.MAlpha)

        self.speech_bubble = DirectLabel(parent=self.actor,
                                         text="",
                                         text_wordwrap=10,
                                         pad=(3, 3),
                                         relief=None,
                                         text_scale=(.3, .3),
                                         pos=(0, 0, 3.6),
                                         frameColor=(.6, .2, .1, .5),
                                         textMayChange=1,
                                         text_frame=(0, 0, 0, 1),
                                         text_bg=(1, 1, 1, 1))
        #self.myImage=
        self.speech_bubble.setTransparency(TransparencyAttrib.MAlpha)
        # stop the speech bubble from being colored like the agent
        self.speech_bubble.setColorScaleOff()
        self.speech_bubble.component('text0').textNode.setCardDecal(1)
        self.speech_bubble.setBillboardAxis()
        # hide the speech bubble from IsisAgent's own camera
        self.speech_bubble.hide(BitMask32.bit(1))

        self.thought_bubble = DirectLabel(parent=self.actor,
                                          text="",
                                          text_wordwrap=9,
                                          text_frame=(1, 0, -2, 1),
                                          text_pos=(0, .5),
                                          text_bg=(1, 1, 1, 0),
                                          relief=None,
                                          frameSize=(0, 1.5, -2, 3),
                                          text_scale=(.18, .18),
                                          pos=(0, 0.2, 3.6),
                                          textMayChange=1,
                                          image=bubble,
                                          image_pos=(0, 0.1, 0),
                                          sortOrder=5)
        self.thought_bubble.setTransparency(TransparencyAttrib.MAlpha)
        # stop the speech bubble from being colored like the agent
        self.thought_bubble.setColorScaleOff()
        self.thought_bubble.component('text0').textNode.setFrameColor(
            1, 1, 1, 0)
        self.thought_bubble.component('text0').textNode.setFrameAsMargin(
            0.1, 0.1, 0.1, 0.1)
        self.thought_bubble.component('text0').textNode.setCardDecal(1)
        self.thought_bubble.setBillboardAxis()
        # hide the thought bubble from IsisAgent's own camera
        self.thought_bubble.hide(BitMask32.bit(1))
        # disable by default
        self.thought_bubble.hide()
        self.thought_filter = {}  # only show thoughts whose values are in here
        self.last_spoke = 0  # timers to keep track of last thought/speech and
        self.last_thought = 0  # hide visualizations

        # put a camera on ralph
        self.fov = NodePath(Camera('RaphViz'))
        self.fov.node().setCameraMask(BitMask32.bit(1))

        # position the camera to be infront of Boxman's face.
        self.fov.reparentTo(self.player_head)
        # x,y,z are not in standard orientation when parented to player-Head
        self.fov.setPos(0, 0.2, 0)
        # if P=0, canrea is looking directly up. 90 is back of head. -90 is on face.
        self.fov.setHpr(0, -90, 0)

        lens = self.fov.node().getLens()
        lens.setFov(60)  #  degree field of view (expanded from 40)
        lens.setNear(0.2)
        #self.fov.node().showFrustum() # displays a box around his head
        #self.fov.place()

        self.prevtime = 0
        self.current_frame_count = 0

        self.isSitting = False
        self.isDisabled = False
        self.msg = None
        self.actorNodePath.setPythonTag("agent", self)

        # Initialize the action queue, with a maximum length of queueSize
        self.queue = []
        self.queueSize = queueSize
        self.lastSense = 0

    def setLayout(self, layout):
        """ Dummy method called by spatial methods for use with objects. 
        Doesn't make sense for an agent that can move around."""
        pass

    def setPos(self, pos):
        """ Wrapper to set the position of the ODE geometry, which in turn 
        sets the visual model's geometry the next time the update() method
        is called. """
        self.setGeomPos(pos)

    def setPosition(self, pos):
        self.setPos(pos)

    def reparentTo(self, parent):
        self.actorNodePath.reparentTo(parent)

    def setControl(self, control, value):
        """Set the state of one of the character's movement controls.  """
        self.controlMap[control] = value

    def get_objects_in_field_of_vision(self, exclude=['isisobject']):
        """ This works in an x-ray style. Fast. Works best if you listen to
        http://en.wikipedia.org/wiki/Rock_Art_and_the_X-Ray_Style while
        you use it.
        
        needs to exclude isisobjects since they cannot be serialized  
        """
        objects = {}
        for obj in base.render.findAllMatches("**/IsisObject*"):
            if not obj.hasPythonTag("isisobj"):
                continue
            o = obj.getPythonTag("isisobj")
            bounds = o.activeModel.getBounds()
            bounds.xform(o.activeModel.getMat(self.fov))
            if self.fov.node().isInView(o.activeModel.getPos(self.fov)):
                pos = o.activeModel.getPos(render)
                pos = (pos[0], pos[1], pos[2] + o.getHeight() / 2)
                p1 = self.fov.getRelativePoint(render, pos)
                p2 = Point2()
                self.fov.node().getLens().project(p1, p2)
                p3 = aspect2d.getRelativePoint(render2d,
                                               Point3(p2[0], 0, p2[1]))
                object_dict = {}
                if 'x_pos' not in exclude: object_dict['x_pos'] = p3[0]
                if 'y_pos' not in exclude: object_dict['y_pos'] = p3[2]
                if 'distance' not in exclude:
                    object_dict['distance'] = o.activeModel.getDistance(
                        self.fov)
                if 'orientation' not in exclude:
                    object_dict['orientation'] = o.activeModel.getH(self.fov)
                if 'actions' not in exclude:
                    object_dict['actions'] = o.list_actions()
                if 'isisobject' not in exclude: object_dict['isisobject'] = o
                # add item to dinctionary
                objects[o] = object_dict
        return objects

    def get_agents_in_field_of_vision(self):
        """ This works in an x-ray vision style as well"""
        agents = {}
        for agent in base.render.findAllMatches("**/agent-*"):
            if not agent.hasPythonTag("agent"):
                continue
            a = agent.getPythonTag("agent")
            bounds = a.actorNodePath.getBounds()
            bounds.xform(a.actorNodePath.getMat(self.fov))
            pos = a.actorNodePath.getPos(self.fov)
            if self.fov.node().isInView(pos):
                p1 = self.fov.getRelativePoint(render, pos)
                p2 = Point2()
                self.fov.node().getLens().project(p1, p2)
                p3 = aspect2d.getRelativePoint(render2d,
                                               Point3(p2[0], 0, p2[1]))
                agentDict = {'x_pos': p3[0],\
                             'y_pos': p3[2],\
                             'distance':a.actorNodePath.getDistance(self.fov),\
                             'orientation': a.actorNodePath.getH(self.fov)}
                agents[a] = agentDict
        return agents

    def in_view(self, isisobj):
        """ Returns true iff a particular isisobject is in view """
        return len(
            filter(lambda x: x['isisobject'] == isisobj,
                   self.get_objects_in_field_of_vision(exclude=[]).values()))

    def get_objects_in_view(self):
        """ Gets objects through ray tracing.  Slow"""
        return self.picker.get_objects_in_view()

    def control__turn_left__start(self, speed=None):
        self.setControl("turn_left", 1)
        self.setControl("turn_right", 0)
        if speed:
            self.speeds[0] = speed
        return "success"

    def control__turn_left__stop(self):
        self.setControl("turn_left", 0)
        return "success"

    def control__turn_right__start(self, speed=None):
        self.setControl("turn_left", 0)
        self.setControl("turn_right", 1)
        if speed:
            self.speeds[1] = speed
        return "success"

    def control__turn_right__stop(self):
        self.setControl("turn_right", 0)
        return "success"

    def control__move_forward__start(self, speed=None):
        self.setControl("move_forward", 1)
        self.setControl("move_backward", 0)
        if speed:
            self.speeds[2] = speed
        return "success"

    def control__move_forward__stop(self):
        self.setControl("move_forward", 0)
        return "success"

    def control__move_backward__start(self, speed=None):
        self.setControl("move_forward", 0)
        self.setControl("move_backward", 1)
        if speed:
            self.speeds[3] = speed
        return "success"

    def control__move_backward__stop(self):
        self.setControl("move_backward", 0)
        return "success"

    def control__move_left__start(self, speed=None):
        self.setControl("move_left", 1)
        self.setControl("move_right", 0)
        if speed:
            self.speeds[4] = speed
        return "success"

    def control__move_left__stop(self):
        self.setControl("move_left", 0)
        return "success"

    def control__move_right__start(self, speed=None):
        self.setControl("move_right", 1)
        self.setControl("move_left", 0)
        if speed:
            self.speeds[5] = speed
        return "success"

    def control__move_right__stop(self):
        self.setControl("move_right", 0)
        return "success"

    def control__look_left__start(self, speed=None):
        self.setControl("look_left", 1)
        self.setControl("look_right", 0)
        if speed:
            self.speeds[9] = speed
        return "success"

    def control__look_left__stop(self):
        self.setControl("look_left", 0)
        return "success"

    def control__look_right__start(self, speed=None):
        self.setControl("look_right", 1)
        self.setControl("look_left", 0)
        if speed:
            self.speeds[8] = speed
        return "success"

    def control__look_right__stop(self):
        self.setControl("look_right", 0)
        return "success"

    def control__look_up__start(self, speed=None):
        self.setControl("look_up", 1)
        self.setControl("look_down", 0)
        if speed:
            self.speeds[6] = speed
        return "success"

    def control__look_up__stop(self):
        self.setControl("look_up", 0)
        return "success"

    def control__look_down__start(self, speed=None):
        self.setControl("look_down", 1)
        self.setControl("look_up", 0)
        if speed:
            self.speeds[7] = speed
        return "success"

    def control__look_down__stop(self):
        self.setControl("look_down", 0)
        return "success"

    def control__jump(self):
        self.setControl("jump", 1)
        return "success"

    def control__view_objects(self):
        """ calls a raytrace to to all objects in view """
        objects = self.get_objects_in_field_of_vision()
        self.control__say(
            "If I were wearing x-ray glasses, I could see %i items" %
            len(objects))
        print "Objects in view:", objects
        return objects

    def control__sense(self):
        """ perceives the world, returns percepts dict """
        percepts = dict()
        # eyes: visual matricies
        #percepts['vision'] = self.sense__get_vision()
        # objects in purview (cheating object recognition)
        percepts['objects'] = self.sense__get_objects()
        # global position in environment - our robots can have GPS :)
        percepts['position'] = self.sense__get_position()
        # language: get last utterances that were typed
        percepts['language'] = self.sense__get_utterances()
        # agents: returns a map of agents to a list of actions that have been sensed
        percepts['agents'] = self.sense__get_agents()
        print percepts
        return percepts

    def control__think(self, message, layer=0):
        """ Changes the contents of an agent's thought bubble"""
        # only say things that are checked in the controller
        if self.thought_filter.has_key(layer):
            self.thought_bubble.show()
            self.thought_bubble['text'] = message
            #self.thought_bubble.component('text0').textNode.setShadow(0.05, 0.05)
            #self.thought_bubble.component('text0').textNode.setShadowColor(self.thought_filter[layer])
            self.last_thought = 0
        return "success"

    def control__say(self, message="Hello!"):
        self.speech_bubble['text'] = message
        self.last_spoke = 0
        return "success"

    """

    Methods explicitly for IsisScenario files 

    """

    def put_in_front_of(self, isisobj):
        # find open direction
        pos = isisobj.getGeomPos()
        direction = render.getRelativeVector(isisobj, Vec3(0, 1.0, 0))
        closestEntry, closestObject = IsisAgent.physics.doRaycastNew(
            'aimRay', 5, [pos, direction], [isisobj.geom])
        print "CLOSEST", closestEntry, closestObject
        if closestObject == None:
            self.setPosition(pos + Vec3(0, 2, 0))
        else:
            print "CANNOT PLACE IN FRONT OF %s BECAUSE %s IS THERE" % (
                isisobj, closestObject)
            direction = render.getRelativeVector(isisobj, Vec3(0, -1.0, 0))
            closestEntry, closestObject = IsisAgent.physics.doRaycastNew(
                'aimRay', 5, [pos, direction], [isisobj.geom])
            if closestEntry == None:
                self.setPosition(pos + Vec3(0, -2, 0))
            else:
                print "CANNOT PLACE BEHIND %s BECAUSE %s IS THERE" % (
                    isisobj, closestObject)
                direction = render.getRelativeVector(isisobj, Vec3(1, 0, 0))
                closestEntry, closestObject = IsisAgent.physics.doRaycastNew(
                    'aimRay', 5, [pos, direction], [isisobj.geom])
                if closestEntry == None:
                    self.setPosition(pos + Vec3(2, 0, 0))
                else:
                    print "CANNOT PLACE TO LEFT OF %s BECAUSE %s IS THERE" % (
                        isisobj, closestObject)
                    # there's only one option left, do it anyway
                    self.setPosition(pos + Vec3(-2, 0, 0))
        # rotate agent to look at it
        self.actorNodePath.setPos(self.getGeomPos())
        self.actorNodePath.lookAt(pos)
        self.setH(self.actorNodePath.getH())

    def put_in_right_hand(self, target):
        return self.pick_object_up_with(target, self.right_hand_holding_object,
                                        self.player_right_hand)

    def put_in_left_hand(self, target):
        return self.pick_object_up_with(target, self.left_hand_holding_object,
                                        self.player_left_hand)

    def __get_object_in_center_of_view(self):
        direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
        pos = self.fov.getPos(render)
        exclude = [
        ]  #[base.render.find("**/kitchenNode*").getPythonTag("isisobj").geom]
        closestEntry, closestObject = IsisAgent.physics.doRaycastNew(
            'aimRay', 5, [pos, direction], exclude)
        return closestObject

    def pick_object_up_with(self, target, hand_slot, hand_joint):
        """ Attaches an IsisObject, target, to the hand joint.  Does not check anything first,
        other than the fact that the hand joint is not currently holding something else."""
        if hand_slot != None:
            print 'already holding ' + hand_slot.getName() + '.'
            return None
        else:
            if target.layout:
                target.layout.remove(target)
                target.layout = None
            # store original position
            target.originalHpr = target.getHpr(render)
            target.disable()  #turn off physics
            if target.body: target.body.setGravityMode(0)
            target.reparentTo(hand_joint)
            target.setPosition(hand_joint.getPos(render))
            target.setTag('heldBy', self.name)
            if hand_joint == self.player_right_hand:
                self.right_hand_holding_object = target
            elif hand_joint == self.player_left_hand:
                self.left_hand_holding_object = target
            hand_slot = target
            return target

    def control__pick_up_with_right_hand(self, target=None):
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return "error: no target in reach"
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        print "attempting to pick up " + target.name + " with right hand.\n"
        if self.can_grasp(target):  # object within distance
            return self.pick_object_up_with(target,
                                            self.right_hand_holding_object,
                                            self.player_right_hand)
        else:
            print 'object (' + target.name + ') is not graspable (i.e. in view and close enough).'
            return 'error: object not graspable'

    def control__pick_up_with_left_hand(self, target=None):
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag("isisobj")
        print "attempting to pick up " + target.name + " with left hand.\n"
        if self.can_grasp(target):  # object within distance
            return self.pick_object_up_with(target,
                                            self.left_hand_holding_object,
                                            self.player_left_hand)
        else:
            print 'object (' + target.name + ') is not graspable (i.e. in view and close enough).'
            return 'error: object not graspable'

    def control__drop_from_right_hand(self):
        print "attempting to drop object from right hand.\n"

        if self.right_hand_holding_object is None:
            print 'right hand is not holding an object.'
            return False
        if self.right_hand_holding_object.getNetTag('heldBy') == self.name:
            self.right_hand_holding_object.reparentTo(render)
            direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
            pos = self.player_right_hand.getPos(render)
            heldPos = self.right_hand_holding_object.geom.getPosition()
            self.right_hand_holding_object.setPosition(pos)
            self.right_hand_holding_object.synchPosQuatToNode()
            self.right_hand_holding_object.setTag('heldBy', '')
            self.right_hand_holding_object.setRotation(
                self.right_hand_holding_object.originalHpr)
            self.right_hand_holding_object.enable()
            if self.right_hand_holding_object.body:
                quat = self.getQuat()
                # throw object
                force = 5
                self.right_hand_holding_object.body.setGravityMode(1)
                self.right_hand_holding_object.getBody().setForce(
                    quat.xform(Vec3(0, force, 0)))
            self.right_hand_holding_object = None
            return 'success'
        else:
            return "Error: not being held by agent %s" % (self.name)

    def control__drop_from_left_hand(self):
        print "attempting to drop object from left hand.\n"
        if self.left_hand_holding_object is None:
            return 'left hand is not holding an object.'
        if self.left_hand_holding_object.getNetTag('heldBy') == self.name:
            self.left_hand_holding_object.reparentTo(render)
            direction = render.getRelativeVector(self.fov, Vec3(0, 1.0, 0))
            pos = self.player_left_hand.getPos(render)
            heldPos = self.left_hand_holding_object.geom.getPosition()
            self.left_hand_holding_object.setPosition(pos)
            self.left_hand_holding_object.synchPosQuatToNode()
            self.left_hand_holding_object.setTag('heldBy', '')
            self.left_hand_holding_object.setRotation(
                self.left_hand_holding_object.originalHpr)
            self.left_hand_holding_object.enable()
            if self.left_hand_holding_object.body:
                quat = self.getQuat()
                # throw object
                force = 5
                self.left_hand_holding_object.body.setGravityMode(1)
                self.left_hand_holding_object.getBody().setForce(
                    quat.xform(Vec3(0, force, 0)))
            self.left_hand_holding_object = None
            return 'success'
        else:
            return "Error: not being held by agent %s" % (self.name)

    def control__use_right_hand(self, target=None, action=None):
        # TODO, rename this to use object with
        if not action:
            if self.msg:
                action = self.msg
            else:
                action = "divide"
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag('isisobj')
        print "Trying to use object", target
        if self.can_grasp(target):
            if (target.call(self, action, self.right_hand_holding_object) or
                (self.right_hand_holding_object and
                 self.right_hand_holding_object.call(self, action, target))):
                return "success"
            return str(action) + " not associated with either target or object"
        return "target not within reach"

    def control__use_left_hand(self, target=None, action=None):
        if not action:
            if self.msg:
                action = self.msg
            else:
                action = "divide"
        if not target:
            target = self.__get_object_in_center_of_view()
            if not target:
                print "no target in reach"
                return
        else:
            target = render.find("**/*" + target + "*").getPythonTag('isisobj')
        if self.can_grasp(target):
            if (target.call(self, action, self.left_hand_holding_object) or
                (self.left_hand_holding_object and
                 self.left_hand_holding_object.call(self, action, target))):
                return "success"
            return str(action) + " not associated with either target or object"
        return "target not within reach"

    def can_grasp(self, isisobject):
        distance = isisobject.activeModel.getDistance(self.fov)
        print "distance = ", distance
        return distance < 5.0

    def is_holding(self, object_name):
        return ((self.left_hand_holding_object and (self.left_hand_holding_object.getPythonTag('isisobj').name  == object_name)) \
             or (self.right_hand_holding_object and (self.right_hand_holding_object.getPythonTag('isisobj').name == object_name)))

    def empty_hand(self):
        if (self.left_hand_holding_object is None):
            return self.player_left_hand
        elif (self.right_hand_holding_object is None):
            return self.player_right_hand
        return False

    def has_empty_hand(self):
        return (self.empty_hand() is not False)

    def control__use_aimed(self):
        """
        Try to use the object that we aim at, by calling its callback method.
        """
        target = self.__get_object_in_center_of_view()
        if target.selectionCallback:
            target.selectionCallback(self, dir)
        return "success"

    def sense__get_position(self):
        x, y, z = self.actorNodePath.getPos()
        h, p, r = self.actorNodePath.getHpr()
        #FIXME
        # neck is not positioned in Blockman nh,np,nr = self.agents[agent_id].actor_neck.getHpr()
        left_hand_obj = ""
        right_hand_obj = ""
        if self.left_hand_holding_object:
            left_hand_obj = self.left_hand_holding_object.getName()
        if self.right_hand_holding_object:
            right_hand_obj = self.right_hand_holding_object.getName()
        return {'body_x': x, 'body_y': y, 'body_z': z,'body_h':h,\
                'body_p': p, 'body_r': r,  'in_left_hand': left_hand_obj, 'in_right_hand':right_hand_obj}

    def sense__get_vision(self):
        self.fov.node().saveScreenshot("temp.jpg")
        image = Image.open("temp.jpg")
        os.remove("temp.jpg")
        return image

    def sense__get_objects(self):
        return dict([x.getName(), y]
                    for (x,
                         y) in self.get_objects_in_field_of_vision().items())

    def sense__get_agents(self):
        curSense = time()
        agents = {}
        for k, v in self.get_agents_in_field_of_vision().items():
            v['actions'] = k.get_other_agents_actions(self.lastSense, curSense)
            agents[k.name] = v
        self.lastSense = curSense
        return agents

    def sense__get_utterances(self):
        """ Clear out the buffer of things that the teacher has typed,
        FIXME: this doesn't work right now """
        return []
        utterances = self.teacher_utterances
        self.teacher_utterances = []
        return utterances

    def debug__print_objects(self):
        text = "Objects in FOV: " + ", ".join(self.sense__get_objects().keys())
        print text

    def add_action_to_history(self, action, args, result=0):
        self.queue.append((time(), action, args, result))
        if len(self.queue) > self.queueSize:
            self.queue.pop(0)

    def get_other_agents_actions(self, start=0, end=None):
        if not end:
            end = time()
        actions = []
        for act in self.queue:
            if act[0] >= start:
                if act[0] < end:
                    actions.append(act)
                else:
                    break
        return actions

    def update(self, stepSize=0.1):
        self.speed = [0.0, 0.0]
        self.actorNodePath.setPos(self.geom.getPosition() + Vec3(0, 0, -0.70))
        self.actorNodePath.setQuat(self.getQuat())
        # the values in self.speeds are used as coefficientes for turns and movements
        if (self.controlMap["turn_left"] != 0):
            self.addToH(stepSize * self.speeds[0])
        if (self.controlMap["turn_right"] != 0):
            self.addToH(-stepSize * self.speeds[1])
        if self.verticalState == 'ground':
            # these actions require contact with the ground
            if (self.controlMap["move_forward"] != 0):
                self.speed[1] = self.speeds[2]
            if (self.controlMap["move_backward"] != 0):
                self.speed[1] = -self.speeds[3]
            if (self.controlMap["move_left"] != 0):
                self.speed[0] = -self.speeds[4]
            if (self.controlMap["move_right"] != 0):
                self.speed[0] = self.speeds[5]
            if (self.controlMap["jump"] != 0):
                kinematicCharacterController.jump(self)
                # one jump at a time!
                self.controlMap["jump"] = 0
        if (self.controlMap["look_left"] != 0):
            self.neck.setR(bound(self.neck.getR(), -60, 60) + stepSize * 80)
        if (self.controlMap["look_right"] != 0):
            self.neck.setR(bound(self.neck.getR(), -60, 60) - stepSize * 80)
        if (self.controlMap["look_up"] != 0):
            self.neck.setP(bound(self.neck.getP(), -60, 80) + stepSize * 80)
        if (self.controlMap["look_down"] != 0):
            self.neck.setP(bound(self.neck.getP(), -60, 80) - stepSize * 80)

        kinematicCharacterController.update(self, stepSize)
        """
        Update the held object position to be in the hands
        """
        if self.right_hand_holding_object != None:
            self.right_hand_holding_object.setPosition(
                self.player_right_hand.getPos(render))
        if self.left_hand_holding_object != None:
            self.left_hand_holding_object.setPosition(
                self.player_left_hand.getPos(render))

        #Update the dialog box and thought windows
        #This allows dialogue window to gradually decay (changing transparancy) and then disappear
        self.last_spoke += stepSize / 2
        self.last_thought += stepSize / 2
        self.speech_bubble['text_bg'] = (1, 1, 1, 1 / (self.last_spoke + 0.01))
        self.speech_bubble['frameColor'] = (.6, .2, .1,
                                            .5 / (self.last_spoke + 0.01))
        if self.last_spoke > 2:
            self.speech_bubble['text'] = ""
        if self.last_thought > 1:
            self.thought_bubble.hide()

        # If the character is moving, loop the run animation.
        # If he is standing still, stop the animation.
        if (self.controlMap["move_forward"] !=
                0) or (self.controlMap["move_backward"] !=
                       0) or (self.controlMap["move_left"] !=
                              0) or (self.controlMap["move_right"] != 0):
            if self.isMoving is False:
                self.isMoving = True
        else:
            if self.isMoving:
                self.current_frame_count = 5.0
                self.isMoving = False

        total_frame_num = self.actor.getNumFrames('walk')
        if self.isMoving:
            self.current_frame_count = self.current_frame_count + (stepSize *
                                                                   250.0)
            if self.current_frame_count > total_frame_num:
                self.current_frame_count = self.current_frame_count % total_frame_num
            self.actor.pose('walk', self.current_frame_count)
        elif self.current_frame_count != 0:
            self.current_frame_count = 0
            self.actor.pose('idle', 0)
        return Task.cont

    def destroy(self):
        self.disable()
        self.specialDirectObject.ignoreAll()
        self.actorNodePath.removeNode()
        del self.specialDirectObject

        kinematicCharacterController.destroy(self)

    def disable(self):
        self.isDisabled = True
        self.geom.disable()
        self.footRay.disable()

    def enable(self):
        self.footRay.enable()
        self.geom.enable()
        self.isDisabled = False

    """
    Set camera to correct height above the center of the capsule
    when crouching and when standing up.
    """

    def crouch(self):
        kinematicCharacterController.crouch(self)
        self.camH = self.crouchCamH

    def crouchStop(self):
        """
        Only change the camera's placement when the KCC allows standing up.
        See the KCC to find out why it might not allow it.
        """
        if kinematicCharacterController.crouchStop(self):
            self.camH = self.walkCamH