def startup(self, **kwargs): self.state = "normal" # this sprite is used to display the item # its also animated to pop out of the backpack self.item_center = self.rect.width * .164, self.rect.height * .13 self.item_sprite = Sprite() self.item_sprite.image = None self.sprites.add(self.item_sprite) # do not move this line super(ItemMenuState, self).startup(**kwargs) self.menu_items.line_spacing = tools.scale(7) # this is the area where the item description is displayed rect = self.game.screen.get_rect() rect.top = tools.scale(106) rect.left = tools.scale(3) rect.width = tools.scale(250) rect.height = tools.scale(32) self.text_area = TextArea(self.font, self.font_color, (96, 96, 128)) self.text_area.rect = rect self.sprites.add(self.text_area, layer=100) # load the backpack icon self.backpack_center = self.rect.width * .16, self.rect.height * .45 self.load_sprite("gfx/ui/item/backpack.png", center=self.backpack_center, layer=100)
def set_font(self, size=5, font=None, color=(10, 10, 10), line_spacing=10): """Set the font properties that the menu uses including font _color, size, typeface, and line spacing. The size and line_spacing parameters will be adjusted the the screen scale. You should pass the original, unscaled values. :param size: The font size in pixels. :param font: Path to the typeface file (.ttf) :param color: A tuple of the RGB _color values :param line_spacing: The spacing in pixels between lines of text :type size: Integer :type font: String :type color: Tuple :type line_spacing: Integer :rtype: None :returns: None .. image:: images/menu/set_font.png """ if font is None: font = prepare.BASEDIR + self.font_filename if size < self.min_font_size: size = self.min_font_size self.line_spacing = tools.scale(line_spacing) self.font_size = tools.scale(size) self.font_color = color self.font = pygame.font.Font(font, self.font_size)
def _initialize_items(self): """ Internal use only. Will reset the items in the menu Reset the menu items and get new updated ones. :rtype: collections.Iterable[MenuItem] """ self.selected_index = 0 self.menu_items.empty() for item in self.initialize_items(): self.menu_items.add(item) if self.menu_items: self.show_cursor() # call item selection change to trigger callback for first time self.on_menu_selection_change() if self.shrink_to_items: center = self.rect.center rect1 = self.menu_items.calc_bounding_rect() rect2 = self.menu_sprites.calc_bounding_rect() rect1 = rect1.union(rect2) # TODO: do not hardcode these values # border is 12, padding is the rest rect1.width += tools.scale(18) rect1.height += tools.scale(19) rect1.topleft = 0, 0 # self.rect = rect1.union(rect2) # self.rect.width += tools.scale(20) # self.rect.topleft = 0, 0 self.rect = rect1 self.rect.center = center self.position_rect()
def animate_capture_monster(self, is_captured, num_shakes, monster): """ Animation for capturing monsters. :param is_captured: boolean representing success of capture :param num_shakes: number of shakes before animation ends :param monster: the monster :return: """ monster_sprite = self._monster_sprite_map.get(monster, None) capdev = self.load_sprite('gfx/items/capture_device.png') animate = partial(self.animate, capdev.rect, transition='in_quad', duration=1.0) scale_sprite(capdev, .4) capdev.rect.center = scale(0), scale(0) animate(x=monster_sprite.rect.centerx) animate(y=monster_sprite.rect.centery) self.task(partial(toggle_visible, monster_sprite), 1.0) # make the monster go away temporarily def kill(): self._monster_sprite_map[monster].kill() self.hud[monster].kill() del self._monster_sprite_map[monster] del self.hud[monster] # TODO: cache this sprite from the first time it's used. # also, should loading animated sprites be more convenient? images = list() for fn in ["capture%02d.png" % i for i in range(1, 10)]: fn = 'animations/technique/' + fn image = tools.load_and_scale(fn) images.append((image, .07)) tech = PygAnimation(images, False) sprite = Sprite() sprite.image = tech sprite.rect = tech.get_rect() self.task(tech.play, 1.0) self.task(partial(self.sprites.add, sprite), 1.0) sprite.rect.midbottom = monster_sprite.rect.midbottom def shake_ball(initial_delay): animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay) animate(capdev.rect, y=scale(3), relative=True) animate = partial(self.animate, duration=0.2, transition='linear', delay=initial_delay + 0.1) animate(capdev.rect, y=-scale(6), relative=True) animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay + 0.3) animate(capdev.rect, y=scale(3), relative=True) for i in range(0, num_shakes): shake_ball(1.8 + i * 1.0) # leave a 0.6s wait between each shake if is_captured: self.task(kill, 2 + num_shakes) else: self.task(partial(toggle_visible, monster_sprite), 1.8 + num_shakes * 1.0) # make the monster appear again! self.task(tech.play, 1.8 + num_shakes * 1.0) self.task(capdev.kill, 1.8 + num_shakes * 1.0)
def shake_ball(initial_delay): animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay) animate(capdev.rect, y=scale(3), relative=True) animate = partial(self.animate, duration=0.2, transition='linear', delay=initial_delay + 0.1) animate(capdev.rect, y=-scale(6), relative=True) animate = partial(self.animate, duration=0.1, transition='linear', delay=initial_delay + 0.3) animate(capdev.rect, y=scale(3), relative=True)
def animate_monster_release_bottom(self, feet, monster): """ :type feet: sequence :type monster: core.components.monster.Monster :return: """ capdev = self.load_sprite('gfx/items/capture_device.png') scale_sprite(capdev, .4) capdev.rect.center = feet[0], feet[1] - scale(60) # animate the capdev falling fall_time = .7 animate = partial(self.animate, duration=fall_time, transition='out_quad') animate(capdev.rect, bottom=feet[1], transition='in_back') animate(capdev, rotation=720, initial=0) # animate the capdev fading away delay = fall_time + .6 fade_duration = .9 h = capdev.rect.height animate = partial(self.animate, duration=fade_duration, delay=delay) animate(capdev, width=1, height=h * 1.5) animate(capdev.rect, y=-scale(14), relative=True) # convert the capdev sprite so we can fade it easily def func(): capdev.image = tools.convert_alpha_to_colorkey(capdev.image) self.animate(capdev.image, set_alpha=0, initial=255, duration=fade_duration) self.task(func, delay) self.task(capdev.kill, fall_time + delay + fade_duration) # load monster and set in final position monster_sprite = self.load_sprite(monster.back_battle_sprite, midbottom=feet) self._monster_sprite_map[monster] = monster_sprite # position monster_sprite off screen and set animation to move it back to final spot monster_sprite.rect.top = self.game.screen.get_height() self.animate(monster_sprite.rect, bottom=feet[1], transition='out_back', duration=.9, delay=fall_time + .5) # capdev opening animation images = list() for fn in ["capture%02d.png" % i for i in range(1, 10)]: fn = 'animations/technique/' + fn image = tools.load_and_scale(fn) images.append((image, .07)) delay = 1.3 tech = PygAnimation(images, False) sprite = Sprite() sprite.image = tech sprite.rect = tech.get_rect() sprite.rect.midbottom = feet self.task(tech.play, delay) self.task(partial(self.sprites.add, sprite), delay)
def draw_hp_bars(self): """ Go through the HP bars and redraw them :returns: None """ for monster, hud in self.hud.items(): rect = pygame.Rect(0, 0, tools.scale(70), tools.scale(8)) rect.right = hud.image.get_width() - tools.scale(8) rect.top += tools.scale(12) self._hp_bars[monster].draw(hud.image, rect)
def animate_sprite_take_damage(self, sprite): """ :type sprite: core.components.sprite.Sprite :return: """ original_x, original_y = sprite.rect.topleft animate = partial(self.animate, sprite.rect, duration=1, transition='in_out_elastic') ani = animate(x=original_x, initial=original_x + scale(400)) ani._elapsed = .735 # just want the end of the animation, not the entire thing ani = animate(y=original_y, initial=original_y - scale(400)) ani._elapsed = .735 # just want the end of the animation, not the entire thing
def animate_parties_in(self): # TODO: break out functions here for each left_trainer, right_trainer = self.players right_monster = right_trainer.monsters[0] surface = pygame.display.get_surface() x, y, w, h = surface.get_rect() # TODO: not hardcode this player, opponent = self.players player_home = self._layout[player]['home'][0] opp_home = self._layout[opponent]['home'][0] y_mod = scale(50) duration = 3 back_island = self.load_sprite('gfx/ui/combat/back_island.png', bottom=opp_home.bottom + y_mod, right=0) monster1 = self.load_sprite(right_monster.front_battle_sprite, bottom=back_island.rect.bottom - scale(12), centerx=back_island.rect.centerx) self.build_hud(self._layout[opponent]['hud'][0], right_monster) self.monsters_in_play[self.players[1]].append(right_monster) self._monster_sprite_map[right_monster] = monster1 self.alert('A wild %s appeared!' % right_monster.name.upper()) front_island = self.load_sprite('gfx/ui/combat/front_island.png', bottom=player_home.bottom - y_mod, left=w) trainer1 = self.load_sprite('gfx/sprites/player/player_front.png', bottom=front_island.rect.centery + scale(6), centerx=front_island.rect.centerx) self._monster_sprite_map[left_trainer] = trainer1 def flip(): monster1.image = pygame.transform.flip(monster1.image, 1, 0) trainer1.image = pygame.transform.flip(trainer1.image, 1, 0) flip() # flip images to opposite self.task(flip, 1.5) # flip the images to proper direction animate = partial(self.animate, transition='out_quad', duration=duration) # top trainer animate(monster1.rect, back_island.rect, centerx=opp_home.centerx) animate(monster1.rect, back_island.rect, y=-y_mod, transition='out_back', relative=True) # bottom trainer animate(trainer1.rect, front_island.rect, centerx=player_home.centerx) animate(trainer1.rect, front_island.rect, y=y_mod, transition='out_back', relative=True)
def calc_menu_items_rect(self): """ Calculate the area inside the internal rect where items are listed :rtype: pygame.Rect """ # WARNING: hardcoded values related to menu arrow size # if menu arrow image changes, this should be adjusted cursor_margin = -tools.scale(11), -tools.scale(5) inner = self.calc_internal_rect() menu_rect = inner.inflate(*cursor_margin) menu_rect.bottomright = inner.bottomright return menu_rect
def animate_monster_leave(self, monster): """ :type monster: core.components.monster.Monster :return: """ sprite = self._monster_sprite_map[monster] if self.get_side(sprite.rect) == "left": x_diff = -scale(150) else: x_diff = scale(150) self.animate(sprite.rect, x=x_diff, relative=True, duration=2)
def calc_inner_rect(rect): """ Calculate the inner rect to draw fg_color that fills bar The values here are calculated based on game scale and the content of the border image file. :param rect: :returns: """ inner = rect.copy() inner.top += tools.scale(2) inner.height -= tools.scale(4) inner.left += tools.scale(9) inner.width -= tools.scale(11) return inner
def get_side(self, rect): """ [WIP] get 'side' of screen rect is in :type rect: pygame.Rect :return: basestring """ return "left" if rect.centerx < scale(100) else "right"
def animate_sprite_tackle(self, sprite): """ :type sprite: core.components.sprite.Sprite :return: """ duration = .3 original_x = sprite.rect.x if self.get_side(sprite.rect) == "left": delta = scale(14) else: delta = -scale(14) self.animate(sprite.rect, x=original_x + delta, duration=duration, transition='out_circ') self.animate(sprite.rect, x=original_x, duration=duration, transition='in_out_circ', delay=.35)
def animate_monster_up(self): ani = self.animate(self.monster_portrait.rect, y=tools.scale(5), duration=1, transition='in_out_quad', relative=True) ani.callback = self.animate_monster_down
def draw_monster_info(self, surface, monster, rect): # position and draw hp bar hp_rect = rect.copy() left = rect.width * .6 right = rect.right - tools.scale(4) hp_rect.width = right - left hp_rect.left = left hp_rect.height = tools.scale(8) hp_rect.centery = rect.centery # draw the hp bar self.hp_bar.value = monster.current_hp / monster.hp self.hp_bar.draw(surface, hp_rect) # draw the name text_rect = rect.inflate(-tools.scale(6), -tools.scale(6)) draw_text(surface, monster.name, text_rect, font=self.font) # draw the level info text_rect.top = rect.bottom - tools.scale(7) draw_text(surface, " Lv " + str(monster.level), text_rect, font=self.font) # draw any status icons # TODO: caching or something, idk # TODO: not hardcode icon sizes for index, status in enumerate(monster.status): if status.icon: image = tools.load_and_scale(status.icon) pos = (rect.width * .4) + (index * tools.scale(32)), rect.y + tools.scale(5) surface.blit(image, pos)
def draw_monster_info(self, surface, monster, rect): # position and draw hp bar hp_rect = rect.copy() hp_rect.width = rect.width // 2 hp_rect.left = rect.centerx - tools.scale(2) hp_rect.height = tools.scale(8) hp_rect.centery = rect.centery self.hp_bar.value = monster.current_hp / monster.hp self.hp_bar.draw(surface, hp_rect) # draw the name text_rect = rect.inflate(-tools.scale(6), -tools.scale(6)) draw_text(surface, monster.name, text_rect, font=self.font) # draw the level info text_rect.top = rect.bottom - tools.scale(6) draw_text(surface, " Lv " + str(monster.level), text_rect, font=self.font)
def add_monster_into_play(self, player, monster): feet = list(self._layout[player]["home"][0].center) feet[1] += tools.scale(11) self.animate_monster_release_bottom(feet, monster) self.build_hud(self._layout[player]["hud"][0], monster) self.monsters_in_play[player].append(monster) # TODO: not hardcode if player is self.players[0]: self.alert("Go %s!" % monster.name.upper()) else: self.alert("A wild %s appeared!" % monster.name.upper())
def add_monster_into_play(self, player, monster): feet = list(self._layout[player]['home'][0].center) feet[1] += tools.scale(11) self.animate_monster_release_bottom(feet, monster) self.build_hud(self._layout[player]['hud'][0], monster) self.monsters_in_play[player].append(monster) # TODO: not hardcode if player is self.players[0]: self.alert('Go %s!' % monster.name.upper()) else: self.alert('A wild %s appeared!' % monster.name.upper())
def animate_party_hud_in(self, player, home, slots): """ Party HUD is the arrow thing with balls. Yes, that one. :param player: the player :type home: pygame.Rect :param slots: Number of slots :return: """ if self.get_side(home) == "left": tray = self.load_sprite('gfx/ui/combat/opponent_party_tray.png', bottom=home.bottom, right=0, layer=hud_layer) self.animate(tray.rect, right=home.right, duration=2, delay=1.5) centerx = home.right - scale(13) offset = scale(8) else: tray = self.load_sprite('gfx/ui/combat/player_party_tray.png', bottom=home.bottom, left=home.right, layer=hud_layer) self.animate(tray.rect, left=home.left, duration=2, delay=1.5) centerx = home.left + scale(13) offset = -scale(8) for index in range(slots): sprite = self.load_sprite('gfx/ui/combat/empty_slot_icon.png', top=tray.rect.top + scale(1), centerx=centerx - index * offset, layer=hud_layer) # convert alpha image to image with a colorkey so we can set_alpha sprite.image = tools.convert_alpha_to_colorkey(sprite.image) sprite.image.set_alpha(0) animate = partial(self.animate, duration=1.5, delay=2.2 + index * .2) animate(sprite.image, set_alpha=255, initial=0) animate(sprite.rect, bottom=tray.rect.top + scale(3))
def initialize_items(self): # get a ref to the combat state combat_state = self.game.get_state_name("CombatState") # TODO: trainer targeting # TODO: cleanup how monster sprites and whatnot are managed # TODO: This is going to work fine for simple matches, but controls will be wonky for parties # TODO: (cont.) Need better handling of cursor keys for 2d layouts of menu items # get all the monster positions # this is used to determine who owns what monsters and what not # TODO: make less duplication of game data in memory, let combat state have more registers, etc self.targeting_map = defaultdict(list) for player, monsters in combat_state.monsters_in_play.items(): for monster in monsters: # TODO: more targeting classes if player == self.player: targeting_class = "own monster" else: targeting_class = "enemy monster" self.targeting_map[targeting_class].append(monster) # TODO: handle odd cases where a situation creates no valid targets # if this target type is not handled by this action, then skip it if targeting_class not in self.action.target: continue # inspect the monster sprite and make a border image for it sprite = combat_state._monster_sprite_map[monster] item = MenuItem(None, None, None, monster) item.rect = sprite.rect.copy() center = item.rect.center item.rect.inflate_ip(tools.scale(16), tools.scale(16)) item.rect.center = center yield item
def startup(self, **kwargs): self.state = "normal" self.item_center = self.rect.width * .164, self.rect.height * .13 self.item_sprite = Sprite() self.item_sprite.image = None self.sprites.add(self.item_sprite) super(ItemMenuState, self).startup(**kwargs) self.menu_items.line_spacing = tools.scale(5) rect = self.game.screen.get_rect() center = rect.center rect.width *= .95 rect.height *= .25 rect.center = center rect.top = tools.scale(110) self.text_area = TextArea(self.font, self.font_color, (96, 96, 128)) self.text_area.rect = rect self.sprites.add(self.text_area, layer=100) # Load the backpack icon self.backpack_center = self.rect.width * .16, self.rect.height * .45 self.load_sprite("gfx/ui/item/backpack.png", center=self.backpack_center, layer=100)
def fit_border(self): """ Resize the window border to fit the contents of the menu :return: """ # get bounding box of menu items and the cursor center = self.rect.center rect1 = self.menu_items.calc_bounding_rect() rect2 = self.menu_sprites.calc_bounding_rect() rect1 = rect1.union(rect2) # expand the bounding box by the border and some padding # TODO: do not hardcode these values # border is 12, padding is the rest rect1.width += tools.scale(18) rect1.height += tools.scale(19) rect1.topleft = 0, 0 # set our rect and adjust the centers to match self.rect = rect1 self.rect.center = center # move the bounding box taking account the anchors self.position_rect()
def animate_party_hud_in(self, player, home): """ Party HUD is the arrow thing with balls. Yes, that one. :param player: the player :type home: pygame.Rect :param slots: Number of slots :return: """ if self.get_side(home) == "left": tray = self.load_sprite('gfx/ui/combat/opponent_party_tray.png', bottom=home.bottom, right=0, layer=hud_layer) self.animate(tray.rect, right=home.right, duration=2, delay=1.5) centerx = home.right - scale(13) offset = scale(8) else: tray = self.load_sprite('gfx/ui/combat/player_party_tray.png', bottom=home.bottom, left=home.right, layer=hud_layer) self.animate(tray.rect, left=home.left, duration=2, delay=1.5) centerx = home.left + scale(13) offset = -scale(8) for index in range(player.party_limit): sprite = None if len(player.monsters) > index: sprite = self.load_sprite( 'gfx/ui/icons/party/party_icon01.png', top=tray.rect.top + scale(1), centerx=centerx - index * offset, layer=hud_layer) else: sprite = self.load_sprite('gfx/ui/combat/empty_slot_icon.png', top=tray.rect.top + scale(1), centerx=centerx - index * offset, layer=hud_layer) # convert alpha image to image with a colorkey so we can set_alpha sprite.image = tools.convert_alpha_to_colorkey(sprite.image) sprite.image.set_alpha(0) animate = partial(self.animate, duration=1.5, delay=2.2 + index * .2) animate(sprite.image, set_alpha=255, initial=0) animate(sprite.rect, bottom=tray.rect.top + scale(3))
def trigger_cursor_update(self, animate=True): """ Force the menu cursor to move into the correct position :param animate: If True, then arrow will move smoothly into position :returns: None or Animation """ selected = self.get_selected_item() if not selected: return x, y = selected.rect.midleft x -= tools.scale(2) if animate: self.remove_animations_of(self.arrow.rect) return self.animate(self.arrow.rect, right=x, centery=y, duration=self.cursor_move_duration) else: self.arrow.rect.midright = x, y return None
def add_monster_into_play(self, player, monster): """ :param player: :param monster: :return: """ # TODO: refactor some into the combat animations feet = list(self._layout[player]['home'][0].center) feet[1] += tools.scale(11) self.animate_monster_release_bottom(feet, monster) self.build_hud(self._layout[player]['hud'][0], monster) self.monsters_in_play[player].append(monster) # TODO: not hardcode if player is self.players[0]: self.alert(trans('combat_call_tuxemon', {"name": monster.name.upper()})) else: self.alert(trans('combat_wild_appeared', {"name": monster.name.upper()}))