def _confirm_option(self) -> Optional[SceneTransition]: if self._selected_option_index < len(self._saved_characters): self.flags.saved_player_state = self._saved_characters[ self._selected_option_index] self.flags.character_file = self._files[ self._selected_option_index] return SceneTransition(self.creating_world_scene(self.flags)) else: return SceneTransition(self.picking_hero_scene(self.flags))
def handle_event(self, event: EngineEvent) -> Optional[SceneTransition]: if event == EngineEvent.PLAYER_DIED: return SceneTransition(self.picking_hero_scene(self.init_flags)) elif event == EngineEvent.ENEMY_DIED: num_enemies = len([npc for npc in self.game_state.non_player_characters if npc.is_enemy]) if num_enemies == 0: return SceneTransition(self.challenge_complete_scene(self.total_time_played)) self.info_message.set_message(str(num_enemies) + " enemies remaining") return None
def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]: map_file_name = self.cmd_flags.map_file_name or "map1.json" map_file_path = "resources/maps/" + map_file_name start_immediately_and_skip_hero_selection = ( self.cmd_flags.chosen_hero_id is not None or self.cmd_flags.hero_start_level is not None or self.cmd_flags.start_money is not None or self.cmd_flags.save_file_name is not None) if start_immediately_and_skip_hero_selection: if self.cmd_flags.save_file_name: saved_player_state = self.save_file_handler.load_player_state_from_json_file( self.cmd_flags.save_file_name) flags = InitFlags(map_file_path=map_file_path, picked_hero=None, saved_player_state=saved_player_state, hero_start_level=None, start_money=None, character_file=None) return SceneTransition( self.scene_factory.creating_world_scene(flags)) if self.cmd_flags.chosen_hero_id: picked_hero = HeroId[self.cmd_flags.chosen_hero_id] else: picked_hero = HeroId.MAGE hero_start_level = int(self.cmd_flags.hero_start_level ) if self.cmd_flags.hero_start_level else 1 start_money = int(self.cmd_flags.start_money ) if self.cmd_flags.start_money else 0 flags = InitFlags(map_file_path=map_file_path, picked_hero=picked_hero, saved_player_state=None, hero_start_level=hero_start_level, start_money=start_money, character_file=None) return SceneTransition( self.scene_factory.creating_world_scene(flags)) else: flags = InitFlags(map_file_path=map_file_path, picked_hero=None, saved_player_state=None, hero_start_level=1, start_money=0, character_file=None) return SceneTransition(self.scene_factory.main_menu_scene(flags))
def handle_user_input(self, events: List[Any]) -> Optional[SceneTransition]: for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT or event.key == pygame.K_UP: self._change_hero(-1) elif event.key == pygame.K_RIGHT or event.key == pygame.K_DOWN: self._change_hero(1) elif event.key == pygame.K_RETURN or event.key == pygame.K_SPACE: self.flags.picked_hero = HEROES[self.selected_hero_index] return SceneTransition( self.creating_world_scene(self.flags))
def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]: # movement speed affects the hero entity in the game state (in contrast to other stats) player_speed_multiplier = self.previous_game_engine.game_state.game_world.player_entity.get_speed_multiplier( ) # NPC's share a "global path finder" that needs to be initialized before we start creating NPCs. # TODO This is very messy path_finder = init_global_path_finder() new_game_engine, new_world_behavior = self.create_new_game_engine_and_behavior( self.previous_game_engine) new_game_state = new_game_engine.game_state path_finder.set_grid(new_game_state.pathfinder_wall_grid) # Must center camera before notifying player position as it affects which walls are shown on the minimap new_game_state.center_camera_on_player() self.ui_view.on_world_area_updated( new_game_state.game_world.entire_world_area) # We set up observers for gameEngine and gameState, since they are newly created in this scene. The player # state's observers (ui view) have already been setup in an earlier scene however. register_game_engine_observers(new_game_engine, self.ui_view) register_game_state_observers(new_game_state, self.ui_view, include_player_state=False) new_game_state.game_world.set_hero_movement_speed( player_speed_multiplier) # Clear any messages to give space for any messages generated by the new world behavior self.ui_view.info_message.clear_messages() # If we don't clear the minimap, there will be remains from the previous game state self.ui_view.minimap.clear_exploration() playing_scene = self.scene_factory.playing_scene( game_state=new_game_state, game_engine=new_game_engine, world_behavior=new_world_behavior, ui_view=self.ui_view, new_hero_was_created=False, character_file=self.character_file, total_time_played_on_character=self.total_time_played_on_character) return SceneTransition(playing_scene)
def control(self, time_passed: Millis) -> Optional[SceneTransition]: if self.game_state.player_state.has_completed_quest( QuestId.MAIN_RETRIEVE_KEY): return SceneTransition(self.victory_screen_scene())
def control(self, time_passed: Millis) -> Optional[SceneTransition]: if self.game_state.player_state.has_finished_main_quest: return SceneTransition(self.victory_screen_scene())
def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]: if not self._saved_characters: return SceneTransition( self.scene_factory.picking_hero_scene(self.flags))
def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]: saved_player_state = self.flags.saved_player_state hero_start_level = self.flags.hero_start_level start_money = self.flags.start_money picked_hero = self.flags.picked_hero map_file_path = self.flags.map_file_path character_file = self.flags.character_file if saved_player_state: hero_from_saved_state = HeroId[saved_player_state.hero_id] if picked_hero is not None and picked_hero != hero_from_saved_state: raise Exception("Mismatch! Hero from saved state: " + str(hero_from_saved_state) + ", but picked hero: " + str(picked_hero)) picked_hero = hero_from_saved_state total_time_played_on_character = saved_player_state.total_time_played_on_character if saved_player_state else 0 # NPC's share a "global path finder" that needs to be initialized before we start creating NPCs. # TODO This is very messy path_finder = GlobalPathFinder() set_global_path_finder(path_finder) map_data = load_map_from_json_file(self.camera_size, map_file_path, picked_hero) path_finder.set_grid(map_data.game_state.pathfinder_wall_grid) game_state = map_data.game_state game_engine = GameEngine(game_state, self.ui_view.info_message) game_state.player_state.exp_was_updated.register_observer( self.ui_view.on_player_exp_updated) game_state.player_state.talents_were_updated.register_observer( self.ui_view.on_talents_updated) game_state.player_state.notify_talent_observers( ) # Must notify the initial state game_state.player_movement_speed_was_updated.register_observer( self.ui_view.on_player_movement_speed_updated) game_state.notify_movement_speed_observers( ) # Must notify the initial state game_state.player_state.stats_were_updated.register_observer( self.ui_view.on_player_stats_updated) game_state.player_state.notify_stats_observers( ) # Must notify the initial state game_engine.talent_was_unlocked.register_observer( self.ui_view.on_talent_was_unlocked) game_engine.ability_was_clicked.register_observer( self.ui_view.on_ability_was_clicked) game_engine.consumable_was_clicked.register_observer( self.ui_view.on_consumable_was_clicked) game_state.player_state.money_was_updated.register_observer( self.ui_view.on_money_updated) game_state.player_state.notify_money_observers( ) # Must notify the initial state game_state.player_state.abilities_were_updated.register_observer( self.ui_view.on_abilities_updated) game_state.player_state.notify_ability_observers( ) # Must notify the initial state game_state.player_state.cooldowns_were_updated.register_observer( self.ui_view.on_cooldowns_updated) game_state.player_state.health_resource.value_was_updated.register_observer( self.ui_view.on_health_updated) game_state.player_state.mana_resource.value_was_updated.register_observer( self.ui_view.on_mana_updated) game_state.player_state.buffs_were_updated.register_observer( self.ui_view.on_buffs_updated) game_state.player_state.quests_were_updated.register_observer( self.ui_view.on_player_quests_updated) game_state.player_entity.movement_changed = Observable() game_state.player_entity.movement_changed.register_observer( play_or_stop_footstep_sounds) game_state.player_entity.position_changed = Observable() game_state.player_entity.position_changed.register_observer( self.ui_view.on_player_position_updated) game_state.player_entity.position_changed.register_observer( lambda _: self.ui_view.on_walls_seen([ w.get_position() for w in game_state.get_walls_in_sight_of_player() ])) self.ui_view.on_world_area_updated(game_state.entire_world_area) # Must center camera before notifying player position as it affects which walls are shown on the minimap game_state.center_camera_on_player() game_state.player_entity.notify_position_observers( ) # Must notify the initial state if map_file_path == 'resources/maps/challenge.json': world_behavior = ChallengeBehavior(self.picking_hero_scene, self.challenge_complete_scene, game_state, self.ui_view.info_message, game_engine, self.flags) else: world_behavior = StoryBehavior(self.victory_screen_scene, game_state, self.ui_view.info_message) if saved_player_state: game_engine.gain_levels(saved_player_state.level - 1) game_state.player_state.gain_exp(saved_player_state.exp) game_engine.set_item_inventory([ ItemType[item] if item else None for item in saved_player_state.items ]) game_state.player_state.consumable_inventory = ConsumableInventory( { int(slot_number): [ConsumableType[c] for c in consumables] for (slot_number, consumables ) in saved_player_state.consumables_in_slots.items() }) game_state.player_state.modify_money(saved_player_state.money) for portal in game_state.portals: if portal.portal_id.name in saved_player_state.enabled_portals: sprite = saved_player_state.enabled_portals[ portal.portal_id.name] portal.activate(Sprite[sprite]) for tier_index, option_index in enumerate( saved_player_state.talent_tier_choices): if option_index is not None: pick_talent(game_state, tier_index, option_index) for completed_quest in saved_player_state.completed_quests: quest = get_quest(QuestId[completed_quest]) game_state.player_state.start_quest(quest) game_state.player_state.complete_quest(quest) for active_quest in saved_player_state.active_quests: quest = get_quest(QuestId[active_quest]) game_state.player_state.start_quest(quest) else: if hero_start_level > 1: game_engine.gain_levels(hero_start_level - 1) if start_money > 0: game_state.player_state.modify_money(start_money) game_state.player_state.item_inventory.was_updated.register_observer( self.ui_view.on_inventory_updated) game_state.player_state.item_inventory.notify_observers( ) # Must notify the initial state game_state.player_state.consumable_inventory.was_updated.register_observer( self.ui_view.on_consumables_updated) game_state.player_state.consumable_inventory.notify_observers( ) # Must notify the initial state self.ui_view.update_hero(game_state.player_state.hero_id) # When loading from a savefile a bunch of messages are generated (levelup, learning talents, etc), but they # are irrelevant, since we're loading an exiting character self.ui_view.info_message.clear_messages() # Talent toggle is highlighted when new talents are unlocked, but we don't want it to be highlighted on startup # when loading from a savefile self.ui_view.remove_highlight_from_talent_toggle() allocate_input_keys_for_abilities(game_state.player_state.abilities) new_hero_was_created = saved_player_state is None playing_scene = self.playing_scene(game_state, game_engine, world_behavior, self.ui_view, new_hero_was_created, character_file, total_time_played_on_character) return SceneTransition(playing_scene)
def handle_user_input(self, events: List[Any]) -> Optional[SceneTransition]: for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: return SceneTransition(self.playing_scene)
def _transition_out_of_dungeon(self): scene = self.scene_factory.switching_game_world( self.game_engine, self.character_file, self.total_time_played_on_character, self._recreate_main_world_engine_and_behavior) return SceneTransition(scene)
def run_one_frame(self, _time_passed: Millis) -> Optional[SceneTransition]: saved_player_state = self.flags.saved_player_state hero_start_level = self.flags.hero_start_level start_money = self.flags.start_money picked_hero = self.flags.picked_hero map_file_path = self.flags.map_file_path character_file = self.flags.character_file if saved_player_state: hero_from_saved_state = HeroId[saved_player_state.hero_id] if picked_hero is not None and picked_hero != hero_from_saved_state: raise Exception("Mismatch! Hero from saved state: " + str(hero_from_saved_state) + ", but picked hero: " + str(picked_hero)) picked_hero = hero_from_saved_state total_time_played_on_character = saved_player_state.total_time_played_on_character if saved_player_state else 0 ui_state = GameUiState() game_state = create_game_state_from_json_file(self.camera_size, map_file_path, picked_hero) game_engine = GameEngine(game_state, ui_state) game_state.player_state.exp_was_updated.register_observer( self.ui_view.on_player_exp_updated) game_state.player_state.talents_were_updated.register_observer( self.ui_view.on_talents_updated) game_state.player_state.notify_talent_observers( ) # Must notify the initial state game_state.player_movement_speed_was_updated.register_observer( self.ui_view.on_player_movement_speed_updated) game_state.notify_movement_speed_observers( ) # Must notify the initial state game_state.player_state.stats_were_updated.register_observer( self.ui_view.on_player_stats_updated) game_state.player_state.notify_stats_observers( ) # Must notify the initial state game_engine.talent_was_unlocked.register_observer( self.ui_view.on_talent_was_unlocked) game_state.player_state.money_was_updated.register_observer( self.ui_view.on_money_updated) game_state.player_state.notify_money_observers( ) # Must notify the initial state game_state.player_state.abilities_were_updated.register_observer( self.ui_view.on_abilities_updated) game_state.player_state.notify_ability_observers( ) # Must notify the initial state game_state.player_state.cooldowns_were_updated.register_observer( self.ui_view.on_cooldowns_updated) game_state.player_state.health_resource.value_was_updated.register_observer( self.ui_view.on_health_updated) game_state.player_state.mana_resource.value_was_updated.register_observer( self.ui_view.on_mana_updated) game_state.player_state.buffs_were_updated.register_observer( self.ui_view.on_buffs_updated) game_state.player_entity.movement_changed = Observable() game_state.player_entity.movement_changed.register_observer( play_or_stop_footstep_sounds) if map_file_path == 'resources/maps/challenge.json': world_behavior = ChallengeBehavior(self.picking_hero_scene, self.challenge_complete_scene, game_state, ui_state, game_engine, self.flags) else: world_behavior = StoryBehavior(self.victory_screen_scene, game_state, ui_state) if saved_player_state: game_engine.gain_levels(saved_player_state.level - 1) game_state.player_state.gain_exp(saved_player_state.exp) game_engine.set_item_inventory([ ItemType[item] if item else None for item in saved_player_state.items ]) game_state.player_state.consumable_inventory = ConsumableInventory( { int(slot_number): [ConsumableType[c] for c in consumables] for (slot_number, consumables ) in saved_player_state.consumables_in_slots.items() }) game_state.player_state.modify_money(saved_player_state.money) for portal in game_state.portals: if portal.portal_id.name in saved_player_state.enabled_portals: sprite = saved_player_state.enabled_portals[ portal.portal_id.name] portal.activate(Sprite[sprite]) for tier_index, option_index in enumerate( saved_player_state.talent_tier_choices): if option_index is not None: pick_talent(game_state, tier_index, option_index) else: if hero_start_level > 1: game_engine.gain_levels(hero_start_level - 1) if start_money > 0: game_state.player_state.modify_money(start_money) game_state.player_state.item_inventory.was_updated.register_observer( self.ui_view.on_inventory_updated) game_state.player_state.item_inventory.notify_observers( ) # Must notify the initial state game_state.player_state.consumable_inventory.was_updated.register_observer( self.ui_view.on_consumables_updated) game_state.player_state.consumable_inventory.notify_observers( ) # Must notify the initial state self.ui_view.update_hero(game_state.player_state.hero_id) # When loading from a savefile a bunch of messages are generated (levelup, learning talents, etc), but they # are irrelevant, since we're loading an exiting character ui_state.clear_messages() # Talent toggle is highlighted when new talents are unlocked, but we don't want it to be highlighted on startup # when loading from a savefile self.ui_view.remove_highlight_from_talent_toggle() allocate_input_keys_for_abilities(game_state.player_state.abilities) new_hero_was_created = saved_player_state is None playing_scene = self.playing_scene(game_state, game_engine, world_behavior, ui_state, self.ui_view, new_hero_was_created, character_file, total_time_played_on_character) return SceneTransition(playing_scene)
def handle_user_input(self, events: List[Any]) -> Optional[SceneTransition]: transition_to_pause = False events_triggered_from_ui: List[EventTriggeredFromUi] = [] # TODO handle dialog/no dialog more explicitly as states, and delegate more things to them (?) if self.ui_view.has_open_dialog(): self.game_state.game_world.player_entity.set_not_moving() user_actions = get_dialog_actions(events) for action in user_actions: if isinstance(action, ActionChangeDialogOption): npc_type, previous_index, new_index = self.ui_view.change_dialog_option( action.index_delta) self._handle_dialog_change_option(npc_type, previous_index, new_index) if isinstance(action, ActionPressSpaceKey): result = self.ui_view.handle_space_click() if result: npc_in_dialog, option_index = result npc_type = npc_in_dialog.npc_type blur_npc_action(npc_type, option_index, self.game_state, self.ui_view) message = select_npc_action(npc_type, option_index, self.game_engine) if message: self.ui_view.info_message.set_message(message) npc_in_dialog.stun_status.remove_one() # User may have been holding down a key when starting dialog, and then releasing it while in # dialog. It's safer then to treat all keys as released when we exit the dialog. self.user_input_handler.forget_held_down_keys() if isinstance(action, ActionMouseMovement): self.ui_view.handle_mouse_movement_in_dialog( action.mouse_screen_position) # we handle "normal UI" mouse movement here primarily so that you can hover your equipment # while in a dialog. (especially important when buying from an NPC) self.ui_view.handle_mouse_movement( action.mouse_screen_position) if isinstance(action, ActionMouseClicked): result = self.ui_view.handle_mouse_click_in_dialog() if result: npc_type, previous_index, new_index = result self._handle_dialog_change_option( npc_type, previous_index, new_index) else: user_actions = self.user_input_handler.get_actions(events) for action in user_actions: if isinstance(action, ActionToggleRenderDebugging): self.render_hit_and_collision_boxes = not self.render_hit_and_collision_boxes # TODO: Handle this better than accessing a global variable from here pythongame.core.pathfinding.npc_pathfinding.DEBUG_RENDER_PATHFINDING = \ not pythongame.core.pathfinding.npc_pathfinding.DEBUG_RENDER_PATHFINDING elif isinstance(action, ActionTryUseAbility): self.game_engine.try_use_ability(action.ability_type) elif isinstance(action, ActionTryUsePotion): self.game_engine.try_use_consumable(action.slot_number) elif isinstance(action, ActionMoveInDirection): self.game_engine.move_in_direction(action.direction) elif isinstance(action, ActionStopMoving): self.game_engine.stop_moving() elif isinstance(action, ActionPauseGame): transition_to_pause = True elif isinstance(action, ActionMouseMovement): self.ui_view.handle_mouse_movement( action.mouse_screen_position) elif isinstance(action, ActionMouseClicked): events_triggered_from_ui += self.ui_view.handle_mouse_click( ) elif isinstance(action, ActionMouseReleased): events_triggered_from_ui += self.ui_view.handle_mouse_release( ) elif isinstance(action, ActionRightMouseClicked): events_triggered_from_ui += self.ui_view.handle_mouse_right_click( ) elif isinstance(action, ActionPressSpaceKey): ready_entity = self.player_interactions_state.get_entity_to_interact_with( ) if ready_entity is not None: if isinstance(ready_entity, NonPlayerCharacter): ready_entity.world_entity.direction = get_directions_to_position( ready_entity.world_entity, self.game_state.game_world.player_entity. get_center_position())[0] ready_entity.world_entity.set_not_moving() ready_entity.stun_status.add_one() npc_type = ready_entity.npc_type dialog_data = get_dialog_data( npc_type, self.game_state) option_index = self.ui_view.start_dialog_with_npc( ready_entity, dialog_data) play_sound(SoundId.DIALOG) hover_npc_action(npc_type, option_index, self.game_state, self.ui_view) elif isinstance(ready_entity, LootableOnGround): self.game_engine.try_pick_up_loot_from_ground( ready_entity) elif isinstance(ready_entity, Portal): self.game_engine.interact_with_portal(ready_entity) elif isinstance(ready_entity, WarpPoint): self.game_engine.use_warp_point(ready_entity) elif isinstance(ready_entity, Chest): self.game_engine.open_chest(ready_entity) elif isinstance(ready_entity, Shrine): self.game_engine.interact_with_shrine(ready_entity) elif isinstance(ready_entity, DungeonEntrance): has_key = self.game_state.player_state.item_inventory.has_item_in_inventory( plain_item_id(ItemType.PORTAL_KEY)) if has_key: entering_dungeon_scene = self.scene_factory.switching_game_world( self.game_engine, self.character_file, self.total_time_played_on_character, self._create_dungeon_engine_and_behavior) return SceneTransition(entering_dungeon_scene) else: self.ui_view.info_message.set_message( "There is a keyhole on the side!") else: raise Exception("Unhandled entity: " + str(ready_entity)) elif isinstance(action, ActionPressKey): events_triggered_from_ui += self.ui_view.handle_key_press( action.key) # TODO Much noise below around playing sounds. Perhaps game_engine should play the sounds in these cases? for event in events_triggered_from_ui: if isinstance(event, StartDraggingItemOrConsumable): play_sound(SoundId.UI_START_DRAGGING_ITEM) elif isinstance(event, DragItemBetweenInventorySlots): did_switch_succeed = self.game_engine.drag_item_between_inventory_slots( event.from_slot, event.to_slot) if did_switch_succeed: play_sound(SoundId.UI_ITEM_WAS_MOVED) else: play_sound(SoundId.INVALID_ACTION) elif isinstance(event, DropItemOnGround): world_position = _get_mouse_world_pos(self.game_state, event.screen_position) self.game_engine.drop_inventory_item_on_ground( event.from_slot, world_position) play_sound(SoundId.UI_ITEM_WAS_DROPPED_ON_GROUND) elif isinstance(event, DragConsumableBetweenInventorySlots): self.game_engine.drag_consumable_between_inventory_slots( event.from_slot, event.to_slot) play_sound(SoundId.UI_ITEM_WAS_MOVED) elif isinstance(event, DropConsumableOnGround): world_position = _get_mouse_world_pos(self.game_state, event.screen_position) self.game_engine.drop_consumable_on_ground( event.from_slot, world_position) play_sound(SoundId.UI_ITEM_WAS_DROPPED_ON_GROUND) elif isinstance(event, PickTalent): name_of_picked = pick_talent(self.game_state, event.tier_index, event.option_index) if not self.game_state.player_state.has_unpicked_talents(): self.ui_view.close_talent_window() self.ui_view.info_message.set_message("Talent picked: " + name_of_picked) play_sound(SoundId.EVENT_PICKED_TALENT) elif isinstance(event, TrySwitchItemInInventory): did_switch_succeed = self.game_engine.try_switch_item_at_slot( event.slot) if did_switch_succeed: play_sound(SoundId.UI_ITEM_WAS_MOVED) else: play_sound(SoundId.INVALID_ACTION) elif isinstance(event, ToggleSound): toggle_muted() elif isinstance(event, SaveGame): self._save_game() elif isinstance(event, ToggleFullscreen): self.toggle_fullscreen_callback() elif isinstance(event, ToggleWindow): play_sound(SoundId.UI_TOGGLE) else: raise Exception("Unhandled event: " + str(event)) if transition_to_pause: return SceneTransition( PausedScene(self, self.world_view, self.ui_view, self.game_state))