def build_screen(self): """ init the pygame window, and build the world screen """ # make the window screen w, h = config_get_screensize() if w < h: log.warn('Screen width (%d px) should be larger' + ' than screen height (%d px).' % (w, h)) self.gui_offset = max(w - h, 0) # left screen space = GUI self.win_size = w, h resolution = w, h self.window = pygame.display.set_mode(resolution) pygame.display.set_caption('CC') # blit the loading screen: a black screen bg = pygame.Surface(resolution) bgcolor = config_get_loadingscreen_bgcolor() bg.fill(bgcolor) bg = bg.convert() self.screen_bg = bg self.window.blit(bg, (0, 0)) # reveal pygame.display.flip() self.charactor_sprites = IndexedLayeredUpdates() # all in game charactors self.active_charactor_sprites = IndexedLayeredUpdates() # chars currently visible on screen self.dmg_sprites = LayeredUpdates() # dmg to display on screen
class MasterView: """ links to presentations of game world and HUD """ def __init__(self, evManager): """ when view starts, register callbacks to events and build the GUI """ self._em = evManager self.register_callbacks() # init pygame's screen pygame.init() #calling init() multiple times does not mess anything self.build_screen() self.build_gui() def register_callbacks(self): """ """ #tick self._em.reg_cb(TickEvent, self.on_tick) # attack self._em.reg_cb(LocalDmgsEvt, self.on_localdmgs) self._em.reg_cb(RemoteCharactorAtkEvt, self.on_remotecharatk) self._em.reg_cb(CharactorRcvDmgEvt, self.on_charrcvdmg) # creepjoin self._em.reg_cb(CreepPlaceEvent, self.add_creep) # death/left self._em.reg_cb(CharactorDeathEvt, self.on_chardeath) self._em.reg_cb(CharactorRemoveEvent, self.on_charremove) # greet self._em.reg_cb(MBuiltMapEvt, self.on_mapbuilt) # hps self._em.reg_cb(MdHpsChangeEvt, self.on_hps) #join self._em.reg_cb(LocalAvatarPlaceEvent, self.on_localavplace) self._em.reg_cb(OtherAvatarPlaceEvent, self.on_remoteavplace) #move self._em.reg_cb(SendMoveEvt, self.on_localavmove) self._em.reg_cb(RemoteCharactorMoveEvent, self.on_remotecharmove) #resu self._em.reg_cb(LocalAvRezEvt, self.on_localavrez) self._em.reg_cb(RemoteCharactorRezEvt, self.on_resurrect) def build_screen(self): """ init the pygame window, and build the world screen """ # make the window screen w, h = config_get_screensize() if w < h: log.warn('Screen width (%d px) should be larger' + ' than screen height (%d px).' % (w, h)) self.gui_offset = max(w - h, 0) # left screen space = GUI self.win_size = w, h resolution = w, h self.window = pygame.display.set_mode(resolution) pygame.display.set_caption('CC') # blit the loading screen: a black screen bg = pygame.Surface(resolution) bgcolor = config_get_loadingscreen_bgcolor() bg.fill(bgcolor) bg = bg.convert() self.screen_bg = bg self.window.blit(bg, (0, 0)) # reveal pygame.display.flip() self.charactor_sprites = IndexedLayeredUpdates() # all in game charactors self.active_charactor_sprites = IndexedLayeredUpdates() # chars currently visible on screen self.dmg_sprites = LayeredUpdates() # dmg to display on screen def build_gui(self): """ Add widgets to the screen. Widgets on the left need only be reblitted when they get dirty. Widgets that overlay the world screen need to be reblitted every frame. """ # start adding widgets leftgui = LayeredDirty() # only reblit when dirty=1 overlaygui = LayeredUpdates() # reblit every frame w, h = self.win_size line_h = config_get_fontsize() gui_w = self.gui_offset # -- name label at top-left of the screen rec = pygame.Rect(0, 0, gui_w - 1, line_h - 1) evt_txt_dict = {MMyNameChangedEvent: 'newname', MGreetNameEvt:'newname'} txtcol = config_get_txtlabel_txtcolor() bgcol = config_get_txtlabel_bgcolor() namebox = TextLabelWidget(self._em, '', events_attrs=evt_txt_dict, rect=rec, txtcolor=txtcol, bgcolor=bgcol) leftgui.add(namebox) # -- list of connected players, until middle of the screen rec = pygame.Rect(0, line_h, gui_w - 1, line_h - 1) txt = 'Connected players:' txtcol = config_get_txtlabel_txtcolor() bgcol = config_get_txtlabel_bgcolor() whosonlinetitle = TextLabelWidget(self._em, txt, rect=rec, txtcolor=txtcol, bgcolor=bgcol) leftgui.add(whosonlinetitle) rec = pygame.Rect(0, 2 * line_h, gui_w - 1, h / 2 - 2 * line_h - 1) numlines = int(rec.height / line_h) txtcol = config_get_chatlog_txtcolor() bgcol = None #config_get_chatlog_bgcolor() whosonlinebox = PlayerListWidget(self._em, numlines, rect=rec, txtcolor=txtcol, bgcolor=bgcol) leftgui.add(whosonlinebox) # -- chat window overlay at bottom of the world screen chat_height = h / 4 numlines = int(chat_height / line_h) if numlines > 0: # text input field rec = pygame.Rect(gui_w + 1, h - line_h, w - gui_w - 1, line_h - 1) chatbox = InputFieldWidget(self._em, rect=rec) overlaygui.add(chatbox) if numlines > 1: # text display line rec = pygame.Rect(gui_w + 1, h * 3 / 4, w - gui_w - 1, h / 4 - line_h - 1) txtcol = config_get_chatlog_txtcolor() # no bg color to disply on top of world screen chatwindow = ChatLogWidget(self._em, numlines=numlines, rect=rec, txtcolor=txtcol) overlaygui.add(chatwindow) self.left_gui_sprites = leftgui self.overlay_gui_sprites = overlaygui ################# gfx utils ############### def center_screen_on_coords(self, cleft, ctop): """ Get the subsurface of interest to pick from the whole world surface and blit it on the window screen. """ self.bg_shift_left = cleft self.bg_shift_top = ctop # compute the rect of the world bg to subsurface from celsize = self.cspr_size subsurf_left = cleft * celsize subsurf_top = ctop * celsize v_diam = self.visib_diam subsurf_dims = (v_diam * celsize, v_diam * celsize) visible_area = pygame.Rect((subsurf_left, subsurf_top), subsurf_dims) # make bg from that area of interest # subsurface() should not raise any error when moving on the world borders # since the screen is squared and the world padded with an extra visib_diam visible_bg = self.world_bg.subsurface(visible_area) visible_bg = visible_bg.convert() self.screen_bg.blit(visible_bg, (self.gui_offset, 0)) self.window.blit(visible_bg, (self.gui_offset, 0)) pygame.display.flip() def game_to_screen_coords(self, gcoords): """ gcoords are cell coords, from the game model. Return the screen coords if in range, or None if outside of range. """ cleft, ctop = gcoords # cell distance from my av cell cell_shift_left = cleft - self.bg_shift_left cell_shift_top = ctop - self.bg_shift_top v_radius = self.visib_rad if abs(cell_shift_left) <= v_radius\ and abs(cell_shift_top) <= v_radius: # in range v_diam = self.visib_diam celsize = self.cspr_size gui_offset = self.gui_offset screenleft = (v_diam / 2 + cell_shift_left) * celsize + gui_offset screentop = (v_diam / 2 + cell_shift_top) * celsize return screenleft, screentop else: # out of range return None, None def display_char_if_inrange(self, char): """ display (or not) a charactor if it's in range. This function is called when the charactor model changes. """ gamecoords = char.cell.coords screenleft, screentop = self.game_to_screen_coords(gamecoords) charspr = self.charactor_sprites.get_spr(char) if screenleft and screentop: # in screen range # ask the charactor's sprite to position itself self.active_charactor_sprites.add(charspr) # activate the spr charspr.update_img(screenleft, screentop) else: # charactor got out of screen: desactivate the spr self.active_charactor_sprites.remove(charspr) def display_dmg_if_inrange(self, char, dmg): """ start displaying the dmg a charactor received """ gcoords = char.cell.coords screenleft, screentop = self.game_to_screen_coords(gcoords) # center of the char spr if screenleft and screentop: celsize = self.cspr_size centerpos = screenleft, screentop - celsize / 4 scroll_height = celsize / 2 # how high should the text scroll until erased txt = str(dmg) duration = config_get_dmgdisplayduration() # in millis color = config_get_dmgfontcolor() fontsize = config_get_dmgfontsize() ScrollingTextSprite(txt, centerpos, scroll_height, duration=duration, fontsize=fontsize, color=color, groups=self.dmg_sprites) ###################### RENDERING OF SPRITES and BG ###################### def on_tick(self, event): """ Render all the dirty sprites. The order is important: blit charactors first, then damage, then GUI. """ chars = self.active_charactor_sprites dmgs = self.dmg_sprites leftgui = self.left_gui_sprites ovgui = self.overlay_gui_sprites win, bg = self.window, self.screen_bg # clear the window from all the sprites, replacing them with the bg chars.clear(win, bg) dmgs.clear(win, bg) leftgui.clear(win, bg) ovgui.clear(win, bg) # update all the sprites - calls update() on each sprite of the groups duration = event.duration # how long passed since last tick chars.update(duration) dmgs.update(duration) leftgui.update(duration) ovgui.update(duration) # collect the display areas that need to be redrawn dirty_chars = chars.draw(win) # ie, all char sprites dirty_dmg = dmgs.draw(win) # all dmg being displayed dirty_lgui = leftgui.draw(win) # but only the left widgets that are dirty dirty_ovgui = ovgui.draw(win) # and all overlay widgets # and redisplay those areas only dirty_rects = dirty_chars + dirty_dmg + dirty_lgui + dirty_ovgui pygame.display.update(dirty_rects) ########### attack ############### def on_localdmgs(self, event): """ Local avatar attacked: only display dmg, but dont update defender's HP bar: the HP update comes from a server msg. """ dmgs = event.dmgs for defer, dmg in dmgs.items(): self.display_dmg_if_inrange(defer, dmg) def on_remotecharatk(self, event): """ Display the charactor attacking. The dmg are displayed by a char RECEIVING dmg, not by the char attacking. """ log.debug('%s attacked' % event.atker.name) # TODO: FT display the charactor attacking instead of logging def on_charrcvdmg(self, event): """ Display text with damage over charactor's sprite. """ defer, dmg = event.defer, event.dmg fromremotechar = event.fromremotechar self.display_char_if_inrange(defer) if fromremotechar: # if atker was local, the dmg was already rendered self.display_dmg_if_inrange(defer, dmg) ####### creepjoin ######### def add_creep(self, event): """ Add creep spr on screen. """ creep = event.creep sprdims = (self.cspr_size, self.cspr_size) bgcolor = config_get_creep_bgcolor() layer = 1 # which layer to put that spr into CharactorSprite(creep, sprdims, bgcolor, layer, self.charactor_sprites) # TODO: FT CreepSprite() instead of CharactorSprite() self.display_char_if_inrange(creep) ########## death ############ def on_chardeath(self, event): """ A charactor died: hide it. Show it again when/if the charactor resurrects. """ char = event.charactor self.display_char_if_inrange(char) log.debug('%s died' % char.name) def on_charremove(self, event): """ A Charactor can be an avatar or a creep. Can be triggered because of local or remote events. """ char = event.charactor charspr = self.charactor_sprites.get_spr(char) charspr.kill() # remove from all sprite groups del charspr ###################### greet ############################### def on_mapbuilt(self, event): """ Build the bg from the map cells, and blit it. The pixel width and height of map cells comes from the window's dimensions and the map's visibility radius. Called only once at the beginning, when model.map has been built. """ worldmap = event.worldmap self.cellsprs = dict() # maps model.Cell to view.CellSprite # determine width and height of cell spr from map visibility r = worldmap.visibility_radius self.visib_rad = r diam = 2 * r + 1 self.visib_diam = diam height = self.win_size[1] celsize = int(height / diam) self.cspr_size = celsize # Build world screen_bg to be scrolled when the avatar moves # so that world_bg can be scrolled. # Padding of visib_diam so that the screen bg can subsurface(world_bg) v_rad = self.visib_rad w_w, w_h = worldmap.width, worldmap.height w_surf_w = celsize * (w_w + 2 * v_rad) w_surf_h = celsize * (w_h + 2 * v_rad) worldbg = pygame.Surface((w_surf_w, w_surf_h)) worldbg.fill(config_get_nonwalkable_color()) for i in range(w_w): for j in range(w_h): # don't forget the visib_rad of padding cspr_left = (v_rad + i) * celsize cspr_top = (v_rad + j) * celsize cellrect = pygame.Rect(cspr_left, cspr_top, celsize, celsize) cell = worldmap.get_cell(i, j) cellspr = CellSprite(cell, cellrect) self.cellsprs[cell] = cellspr # blit the cell to world bg worldbg.blit(cellspr.image, cellspr.rect) worldbg = worldbg.convert() self.world_bg = worldbg # Center screen screen_bg on entrance cell, and blit eleft, etop = worldmap.entrance_coords self.center_screen_on_coords(eleft, etop) ################ hps #################### def on_hps(self, event): """ A char changed hps. Update its life bar. """ char = event.charactor self.display_char_if_inrange(char) log.debug('%s changed hps to %d/%d' % (char.name, char.hp, char.maxhp)) ############### join ############# def on_localavplace(self, event): """ Center the map on the avatar's cell, build a charactor sprite in that cell, and reblit screen_bg, charactor sprites, and GUI. """ avatar = event.avatar sprdims = (self.cspr_size, self.cspr_size) bgcolor = config_get_myav_bgcolor() layer = 3 # local avatar sprite is over other avs, and over creeps CharactorSprite(avatar, sprdims, bgcolor, layer, self.charactor_sprites) cleft, ctop = avatar.cell.coords self.center_screen_on_coords(cleft, ctop) #must be done before display_char self.display_char_if_inrange(avatar) def on_remoteavplace(self, event): """ Make a sprite and center the sprite based on the cell location of the remote avatar. """ av = event.avatar sprdims = (self.cspr_size, self.cspr_size) bgcolor = config_get_avdefault_bgcolor() layer = 2 # other av sprites are over creeps but below the local av spr CharactorSprite(av, sprdims, bgcolor, layer, self.charactor_sprites) self.display_char_if_inrange(av) ################# move ################ def on_localavmove(self, event): """ move my avatar: scroll the screen_bg """ myav = event.avatar cleft, ctop = myav.cell.coords self.center_screen_on_coords(cleft, ctop) # redisplay the charactors that are in range for charspr in self.charactor_sprites: char = charspr.char if char.cell: # char is still alive self.display_char_if_inrange(char) def on_remotecharmove(self, event): """ Move the spr of creeps or other avatars. """ char = event.charactor self.display_char_if_inrange(char) ################# resurrect ############ def on_localavrez(self, event): """ Resurrect my avatar: display my av, and center screen on my av. Note: it can happen that, within the same frame, the avatar resurrects and is killed again (when the server sent rez and death msg in the same frame). Don't display the avatar if it is dead again/still dead. """ # center screen on my av and redisplay all chars if my av resurrected myav = event.avatar if myav.cell: # av has not been killed again in the same frame cleft, ctop = myav.cell.coords self.center_screen_on_coords(cleft, ctop) #must be done before display_char # redisplay all the charactors that are alive (includes me) for charspr in self.charactor_sprites: char = charspr.char if char.cell: # char is still alive self.display_char_if_inrange(char) def on_resurrect(self, event): """ Resurrect another charactor (creep or avatar) """ char = event.charactor self.display_char_if_inrange(char) log.debug('%s resurrected' % event.charactor.name)