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
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
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
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
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()
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
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
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()
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
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
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