def host_game(self): # check if server is already hosting a game if self.game.server.listening: self.game.pop_state(self) open_dialog(local_session, [T.translate('multiplayer_already_hosting')]) # not hosting, so start the process elif not self.game.isclient: # Configure this game to host self.game.ishost = True self.game.server.server.listen() self.game.server.listening = True # Enable the game, so we can connect to self self.game.client.enable_join_multiplayer = True self.game.client.client.listen() self.game.client.listening = True # connect to self while not self.game.client.client.registered: self.game.client.client.autodiscover(autoregister=False) for game in self.game.client.client.discovered_servers: self.game.client.client.register(game) # close this menu self.game.pop_state(self) # inform player that hosting is ready open_dialog(local_session, [T.translate('multiplayer_hosting_ready')])
def prompt_for_name(self, menu_item): self.monster = menu_item.game_object self.session.client.push_state( state_name="InputMenu", prompt=T.translate("input_monster_name"), callback=self.set_monster_name, escape_key_exits=False, initial=T.translate(self.monster.slug))
def load(self, slug): """Loads and sets this items's attributes from the item.db database. The item is looked up in the database by slug. :param slug: The item slug to look up in the monster.item database. :type slug: String :rtype: None :returns: None **Examples:** >>> potion = Item() >>> potion.load('potion') # Load an item by slug. >>> pprint.pprint(potion.__dict__) { 'description': u'Heals a monster by 50 HP.', 'effects': [u'heal'], 'slug': 'potion', 'name': u'potion', 'sprite': u'resources/gfx/items/potion.png', 'surface': <Surface(66x90x32 SW)>, 'surface_size_original': (66, 90), 'type': u'Consumable' } """ try: results = db.lookup(slug, table="item") except KeyError: logger.error(msg="Failed to find item with slug {}".format(slug)) return self.slug = results["slug"] # short English identifier self.name = T.translate(self.slug) # translated name self.description = T.translate("{}_description".format( self.slug)) # will be locale string # item use notifications (translated!) self.use_item = T.translate(results["use_item"]) self.use_success = T.translate(results["use_success"]) self.use_failure = T.translate(results["use_failure"]) # misc attributes (not translated!) self.sort = results['sort'] assert self.sort self.type = results["type"] self.sprite = results["sprite"] self.usable_in = results["usable_in"] self.target = process_targets(results["target"]) self.effects = self.parse_effects(results.get("effects", [])) self.conditions = self.parse_conditions(results.get("conditions", [])) self.surface = graphics.load_and_scale(self.sprite) self.surface_size_original = self.surface.get_size()
def ask_confirmation(): # open menu to confirm the save menu = self.client.push_state("Menu") menu.shrink_to_items = True # add choices yes = MenuItem(self.shadow_text(T.translate('save_overwrite')), None, None, positive_answer) no = MenuItem(self.shadow_text(T.translate('save_keep')), None, None, negative_answer) menu.add(yes) menu.add(no)
def load(self, slug): """Loads and sets this technique's attributes from the technique database. The technique is looked up in the database by slug. :param slug: The slug of the technique to look up in the database. :type slug: String :rtype: None """ results = db.lookup(slug, table="technique") self.slug = results["slug"] # a short English identifier self.name = T.translate(self.slug) # locale-specific string self.sort = results['sort'] # technique use notifications (translated!) # NOTE: should be `self.use_tech`, but Technique and Item have overlapping checks self.use_item = T.maybe_translate(results.get("use_tech")) self.use_success = T.maybe_translate(results.get("use_success")) self.use_failure = T.maybe_translate(results.get("use_failure")) self.category = results["category"] self.icon = results["icon"] self._combat_counter = 0 self._life_counter = 0 if results.get('types'): self.type1 = results["types"][0] if len(results['types']) > 1: self.type2 = results["types"][1] else: self.type2 = None else: self.type1 = self.type2 = None self.power = results.get("power") self.is_fast = results.get("is_fast") self.recharge_length = results.get("recharge") self.is_area = results.get("is_area") self.range = results.get("range") self.accuracy = results.get("accuracy") self.potency = results.get("potency") self.effect = results["effects"] self.target = process_targets(results["target"]) # Load the animation sprites that will be used for this technique self.animation = results["animation"] if self.animation: self.images = [] animation_dir = prepare.fetch("animations", "technique") directory = sorted(os.listdir(animation_dir)) for image in directory: if self.animation and image.startswith(self.animation): self.images.append( os.path.join("animations/technique", image)) # Load the sound effect for this technique self.sfx = results["sfx"]
def ask_player_for_monster(self, player): """ Open dialog to allow player to choose a TXMN to enter into play :param player: :return: """ def add(menuitem): monster = menuitem.game_object if monster.current_hp == 0: tools.open_dialog(local_session, [ T.format("combat_fainted", parameters={"name": monster.name}) ]) elif monster in self.active_monsters: tools.open_dialog(local_session, [ T.format("combat_isactive", parameters={"name": monster.name}) ]) msg = T.translate("combat_replacement_is_fainted") tools.open_dialog(local_session, [msg]) else: self.add_monster_into_play(player, monster) self.client.pop_state() state = self.client.push_state("MonsterMenuState") # must use a partial because alert relies on a text box that may not exist # until after the state hs been startup state.task(partial(state.alert, T.translate("combat_replacement")), 0) state.on_menu_selection = add state.escape_key_exits = False
def save(self): logger.info("Saving!") try: save_data = save.get_save_data(local_session, ) save.save( save_data, self.selected_index + 1, ) save.slot_number = self.selected_index except Exception as e: raise logger.error("Unable to save game!!") logger.error(e) open_dialog(local_session, [T.translate('save_failure')]) else: open_dialog(local_session, [T.translate('save_success')])
def render_empty_slot(self, rect): slot_image = pygame.Surface(rect.size, pygame.SRCALPHA) rect = rect.move(0, rect.height // 2 - 10) text.draw_text(slot_image, T.translate('empty_slot'), rect, font=self.font) return slot_image
def initialize_items(self): menu_items_map = (('menu_fight', self.open_technique_menu), ('menu_monster', self.open_swap_menu), ('menu_item', self.open_item_menu), ('menu_run', self.run)) for key, callback in menu_items_map: label = T.translate(key).upper() image = self.shadow_text(label) yield MenuItem(image, label, None, callback)
def initialize_items(self): servers = self.game.client.server_list if servers: for server in servers: label = self.shadow_text(server) yield MenuItem(label, None, None, None) else: label = self.shadow_text(T.translate('multiplayer_no_servers')) item = MenuItem(label, None, None, None) item.enabled = False yield item
def add(menuitem): monster = menuitem.game_object if monster.current_hp == 0: tools.open_dialog(local_session, [T.format("combat_fainted", parameters={"name": monster.name})]) elif monster in self.active_monsters: tools.open_dialog(local_session, [T.format("combat_isactive", parameters={"name": monster.name})]) msg = T.translate("combat_replacement_is_fainted") tools.open_dialog(local_session, [msg]) else: self.add_monster_into_play(player, monster) self.client.pop_state()
def new_game(): # load the starting map state = self.client.replace_state("WorldState") map_name = prepare.fetch("maps", prepare.CONFIG.starting_map) state.change_map(map_name) self.client.push_state( state_name="InputMenu", prompt=T.translate("input_name"), callback=set_player_name, escape_key_exits=False, ) self.client.push_state("FadeInTransition")
def show_item_result_as_dialog(session, item, result): """ Show generic dialog if item was used or not :param tuxemon.core.session.Session session: Session :param tuxemon.core.item.item.Item item: Item :param dict result: :return: None """ msg_type = 'use_success' if result['success'] else 'use_failure' template = getattr(item, msg_type) if template: message = T.translate(template) open_dialog(session, [message])
def open_choice_menu(): # open the menu for use/cancel menu = self.client.push_state("Menu") menu.shrink_to_items = True menu_items_map = (('item_confirm_use', confirm), ('item_confirm_cancel', cancel)) # add our options to the menu for key, callback in menu_items_map: label = T.translate(key).upper() image = self.shadow_text(label) item = MenuItem(image, label, None, callback) menu.add(item)
def initialize_items(self): empty_image = None rect = self.client.screen.get_rect() slot_rect = Rect(0, 0, rect.width * 0.80, rect.height // 6) for i in range(self.number_of_slots): # Check to see if a save exists for the current slot if os.path.exists(prepare.SAVE_PATH + str(i + 1) + ".save"): image = self.render_slot(slot_rect, i + 1) item = MenuItem(image, T.translate('menu_save'), None, None) self.add(item) else: if not empty_image: empty_image = self.render_empty_slot(slot_rect) item = MenuItem(empty_image, "SAVE", None, None) self.add(item)
def render_slot(self, rect, slot_num): slot_image = pygame.Surface(rect.size, pygame.SRCALPHA) # Try and load the save game and draw details about the save save_data = save.load(slot_num) if "screenshot" in save_data: screenshot = b64decode(save_data["screenshot"]) size = save_data["screenshot_width"], save_data[ "screenshot_height"] thumb_image = pygame.image.fromstring(screenshot, size, "RGB").convert() thumb_rect = thumb_image.get_rect().fit(rect) thumb_image = pygame.transform.smoothscale(thumb_image, thumb_rect.size) else: thumb_rect = rect.copy() thumb_rect.width /= 5.0 thumb_image = pygame.Surface(thumb_rect.size) thumb_image.fill((255, 255, 255)) if "error" in save_data: red = (255, 0, 0) pygame.draw.line(thumb_image, red, [0, 0], thumb_rect.size, 3) pygame.draw.line(thumb_image, red, [0, thumb_rect.height], [thumb_rect.width, 0], 3) # Draw the screenshot slot_image.blit(thumb_image, (rect.width * .20, 0)) # Draw the slot text rect = rect.move(0, rect.height // 2 - 10) text.draw_text(slot_image, T.translate('slot') + " " + str(slot_num), rect, font=self.font) x = int(rect.width * .5) text.draw_text(slot_image, save_data['player_name'], (x, 0, 500, 500), font=self.font) if "error" not in save_data: text.draw_text(slot_image, save_data['time'], (x, 50, 500, 500), font=self.font) return slot_image
def start(self): def set_variable(var_value): player.game_variables[self.parameters.variable] = var_value self.session.client.pop_state() # perform text substitutions choices = replace_text(self.session, self.parameters.choices) player = get_npc(self.session, "player") # make menu options for each string between the colons var_list = choices.split(":") var_menu = list() for val in var_list: text = T.translate(val) var_menu.append((text, text, partial(set_variable, val))) self.open_choice_dialog(self.session, var_menu)
def startup(self, *args, **kwargs): # If there is a save, then move the cursor to "Load game" first index = get_index_of_latest_save() kwargs['selected_index'] = 0 if index is None else 1 super(StartState, self).startup(*args, **kwargs) def change_state(state, **change_state_kwargs): return partial(self.client.push_state, state, **change_state_kwargs) def set_player_name(text): local_session.player.name = text def new_game(): # load the starting map state = self.client.replace_state("WorldState") map_name = prepare.fetch("maps", prepare.CONFIG.starting_map) state.change_map(map_name) self.client.push_state( state_name="InputMenu", prompt=T.translate("input_name"), callback=set_player_name, escape_key_exits=False, ) self.client.push_state("FadeInTransition") def options(): pass def exit_game(): self.client.exit = True menu_items_map = ( ('menu_new_game', new_game), ('menu_load', change_state("LoadMenuState")), ('menu_options', options), ('exit', exit_game), ) for key, callback in menu_items_map: label = T.translate(key).upper() image = self.shadow_text(label) item = MenuItem(image, label, None, callback) self.add(item)
def run(self): """ Cause player to run from the battle TODO: only works for player0 :return: None """ # TODO: only works for player0 self.client.pop_state(self) combat_state = self.client.get_state_by_name("CombatState") if combat_state.is_trainer_battle: def open_menu(): combat_state.task( partial(combat_state.show_monster_action_menu, self.monster), 1 ) combat_state.alert(T.translate("combat_can't_run_from_trainer"), open_menu) else: combat_state.trigger_player_run(combat_state.players[0])
def add_menu_items(state, items): for key, callback in items: label = T.translate(key).upper() state.build_item(label, callback)
def open_monster_stats(): open_dialog(local_session, [T.translate('not_implemented')])
def join_by_ip(self): self.game.push_state("InputMenu", prompt=T.translate("multiplayer_join_prompt"))
def add_menu_items(state, items): for key, callback in items: label = T.translate(key).upper() image = state.shadow_text(label) item = MenuItem(image, label, None, callback) state.add(item)
def __init__(self, npc_slug, sprite_name=None, combat_front=None, combat_back=None): super().__init__() # load initial data from the npc database npc_data = db.lookup(npc_slug, table="npc") self.slug = npc_slug # This is the NPC's name to be used in dialog self.name = T.translate(self.slug) # use 'animations' passed in # Hold on the the string so it can be sent over the network self.sprite_name = sprite_name self.combat_front = combat_front self.combat_back = combat_back if self.sprite_name is None: # Try to use the sprites defined in the JSON data self.sprite_name = npc_data["sprite_name"] if self.combat_front is None: self.combat_front = npc_data["combat_front"] if self.combat_back is None: self.combat_back = npc_data["combat_back"] # general self.behavior = "wander" # not used for now self.game_variables = {} # Tracks the game state self.interactions = [] # List of ways player can interact with the Npc self.isplayer = False # used for various tests, idk self.monsters = [ ] # This is a list of tuxemon the npc has. Do not modify directly self.inventory = {} # The Player's inventory. # Variables for long-term item and monster storage # Keeping these seperate so other code can safely # assume that all values are lists self.monster_boxes = dict() self.item_boxes = dict() # combat related self.ai = None # Whether or not this player has AI associated with it self.speed = 10 # To determine combat order (not related to movement!) self.moves = [] # list of techniques # pathfinding and waypoint related self.pathfinding = None self.path = [] self.final_move_dest = [ 0, 0 ] # Stores the final destination sent from a client # This is used to 'set back' when lost, and make movement robust. # If entity falls off of map due to a bug, it can be returned to this value. # When moving to a waypoint, this is used to detect if movement has overshot # the destination due to speed issues or framerate jitters. self.path_origin = None # movement related self.move_direction = None # Set this value to move the npc (see below) self.facing = "down" # Set this value to change the facing direction self.moverate = CONFIG.player_walkrate # walk by default self.ignore_collisions = False # What is "move_direction"? # Move direction allows other functions to move the npc in a controlled way. # To move the npc, change the value to one of four directions: left, right, up or down. # The npc will then move one tile in that direction until it is set to None. # TODO: move sprites into renderer so class can be used headless self.playerHeight = 0 self.playerWidth = 0 self.standing = {} # Standing animation frames self.sprite = {} # Moving animation frames self.moveConductor = pyganim.PygConductor() self.load_sprites() self.rect = Rect( self.tile_pos, (self.playerWidth, self.playerHeight)) # Collision rect
def not_implemented_dialog(): open_dialog(local_session, [T.translate('not_implemented')])
def perform_action(self, user, technique, target=None): """ Do something with the thing: animated :param user: :param technique: Not a dict: a Technique or Item :param target: :returns: """ technique.advance_round() # This is the time, in seconds, that the animation takes to finish. action_time = 3.0 result = technique.use(user, target) if technique.use_item: # "Monster used move!" context = { "user": getattr(user, "name", ''), "name": technique.name, "target": target.name } message = T.format(technique.use_item, context) else: message = '' try: audio.load_sound(technique.sfx).play() except AttributeError: pass # action is performed, so now use sprites to animate it # this value will be None if the target is off screen target_sprite = self._monster_sprite_map.get(target, None) # slightly delay the monster shake, so technique animation # is synchronized with the damage shake motion hit_delay = 0 if user: # TODO: a real check or some params to test if should tackle, etc if result["should_tackle"]: hit_delay += .5 user_sprite = self._monster_sprite_map[user] self.animate_sprite_tackle(user_sprite) if target_sprite: self.task( partial(self.animate_sprite_take_damage, target_sprite), hit_delay + .2) self.task(partial(self.blink, target_sprite), hit_delay + .6) # TODO: track total damage # Track damage self._damage_map[target].add(user) element_damage_key = MULT_MAP.get(result['element_multiplier']) if element_damage_key: m = T.translate(element_damage_key) message += "\n" + m for status in result.get("statuses", []): m = T.format( status.use_item, { "name": technique.name, "user": status.link.name if status.link else "", "target": status.carrier.name }) message += "\n" + m else: # assume this was an item used # handle the capture device if result["capture"]: message += "\n" + T.translate('attempting_capture') action_time = result["num_shakes"] + 1.8 self.animate_capture_monster(result["success"], result["num_shakes"], target) # TODO: Don't end combat right away; only works with SP, and 1 member parties # end combat right here if result["success"]: self.task(self.end_combat, action_time + 0.5) # Display 'Gotcha!' first. self.task(partial(self.alert, T.translate('gotcha')), action_time) self._animation_in_progress = True return # generic handling of anything else else: msg_type = 'use_success' if result[ 'success'] else 'use_failure' template = getattr(technique, msg_type) if template: message += "\n" + T.translate(template) self.alert(message) self.suppress_phase_change(action_time) else: if result["success"]: self.suppress_phase_change() self.alert( T.format('combat_status_damage', { "name": target.name, "status": technique.name })) if result["success"] and target_sprite and technique.images: tech_sprite = self.get_technique_animation(technique) tech_sprite.rect.center = target_sprite.rect.center self.task(tech_sprite.image.play, hit_delay) self.task(partial(self.sprites.add, tech_sprite, layer=50), hit_delay) self.task(tech_sprite.kill, 3)
def load_from_db(self, slug): """Loads and sets this monster's attributes from the monster.db database. The monster is looked up in the database by name. :param slug: Slug to lookup :type slug: str :rtype: None """ # Look up the monster by name and set the attributes in this instance results = db.lookup(slug, table="monster") if results is None: logger.error("monster {} is not found".format(slug)) raise RuntimeError self.slug = results["slug"] # short English identifier self.name = T.translate(results["slug"]) # translated name self.description = T.translate("{}_description".format( results["slug"])) # translated description self.category = T.translate(results["category"]) # translated category self.shape = results.get("shape", "landrace").lower() types = results.get("types") if types: self.type1 = results["types"][0].lower() if len(types) > 1: self.type2 = results["types"][1].lower() self.weight = results['weight'] # Look up the moves that this monster can learn AND LEARN THEM. moveset = results.get("moveset") if moveset: for move in moveset: self.moveset.append(move) if move['level_learned'] >= self.level: technique = Technique(move['technique']) self.learn(technique) # Look up the evolutions for this monster. evolutions = results.get("evolutions") if evolutions: for evolution in evolutions: self.evolutions.append(evolution) # Look up the monster's sprite image paths self.front_battle_sprite = self.get_sprite_path( results['sprites']['battle1']) self.back_battle_sprite = self.get_sprite_path( results['sprites']['battle2']) self.menu_sprite_1 = self.get_sprite_path(results['sprites']['menu1']) self.menu_sprite_2 = self.get_sprite_path(results['sprites']['menu2']) # get sound slugs for this monster, defaulting to a generic type-based sound self.combat_call = results.get("sounds", {}).get( "combat_call", "sound_{}_call".format(self.type1)) self.faint_call = results.get("sounds", {}).get( "faint_call", "sound_{}_faint".format(self.type1)) # Load the monster AI # TODO: clean up AI 'core' loading and what not ai_result = results['ai'] if ai_result == "SimpleAI": self.ai = ai.SimpleAI() elif ai_result == "RandomAI": self.ai = ai.RandomAI()
def transition_phase(self, phase): """ Change from one phase from another. Part of state machine * Will be run just -once- when phase changes. * Do not change phase. * Execute code only to change into new phase. * The phase's update will be executed -after- this :param phase: :return: """ if phase == "housekeeping phase": self._round += 1 # fill all battlefield positions, but on round 1, don't ask self.fill_battlefield_positions(ask=self._round > 1) # record the useful properties of the last monster we fought monster_record = self.monsters_in_play[self.players[1]][0] if monster_record in self.active_monsters: self.players[0].game_variables[ 'battle_last_monster_name'] = monster_record.name self.players[0].game_variables[ 'battle_last_monster_level'] = monster_record.level self.players[0].game_variables[ 'battle_last_monster_type'] = monster_record.slug self.players[0].game_variables[ 'battle_last_monster_category'] = monster_record.category self.players[0].game_variables[ 'battle_last_monster_shape'] = monster_record.shape if phase == "decision phase": self.reset_status_icons() if not self._decision_queue: for player in self.human_players: # the decision queue tracks human players who need to choose an # action self._decision_queue.extend(self.monsters_in_play[player]) for trainer in self.ai_players: for monster in self.monsters_in_play[trainer]: action = self.get_combat_decision_from_ai(monster) self._action_queue.append(action) elif phase == "action phase": self.sort_action_queue() elif phase == "post action phase": # apply status effects to the monsters for monster in self.active_monsters: for technique in monster.status: self.enqueue_action(None, technique, monster) elif phase == "resolve match": pass elif phase == "ran away": self.players[0].set_party_status() self.players[0].game_variables['battle_last_result'] = 'ran' self.alert(T.translate('combat_player_run')) # after 3 seconds, push a state that blocks until enter is pressed # after the state is popped, the combat state will clean up and close # if you run in PvP, you need "defeated message" self.task(partial(self.client.push_state, "WaitForInputState"), 2) self.suppress_phase_change(3) elif phase == "draw match": self.players[0].set_party_status() self.players[0].game_variables['battle_last_result'] = 'draw' # it is a draw match; both players were defeated in same round self.alert(T.translate('combat_draw')) # after 3 seconds, push a state that blocks until enter is pressed # after the state is popped, the combat state will clean up and close self.task(partial(self.client.push_state, "WaitForInputState"), 2) self.suppress_phase_change(3) elif phase == "has winner": # TODO: proper match check, etc # This assumes that player[0] is the human playing in single player self.players[0].set_party_status() if self.remaining_players[0] == self.players[0]: self.players[0].game_variables['battle_last_result'] = 'won' self.alert(T.translate('combat_victory')) else: self.players[0].game_variables['battle_last_result'] = 'lost' self.players[0].game_variables['battle_lost_faint'] = 'true' self.alert(T.translate('combat_defeat')) # after 3 seconds, push a state that blocks until enter is pressed # after the state is popped, the combat state will clean up and close self.task(partial(self.client.push_state, "WaitForInputState"), 2) self.suppress_phase_change(3) elif phase == "end combat": self.players[0].set_party_status() self.end_combat()
def start(self): self.session.client.push_state(state_name="InputMenu", prompt=T.translate("input_name"), callback=self.set_player_name, escape_key_exits=False, initial=self.session.player.name)