def register_dwarf_npc(): size = ( 30, 30 ) # Must not align perfectly with grid cell size (pathfinding issues) sprite = Sprite.NEUTRAL_NPC_DWARF npc_type = NpcType.NEUTRAL_DWARF movement_speed = 0.03 register_npc_data(npc_type, NpcData.neutral(sprite, size, movement_speed)) register_npc_behavior(npc_type, NpcMind) introduction = "Hello there. I'm always looking for treasure. If you find any, we might be able to strike a deal!" dialog_options = [ sell_item_option( plain_item_id(ItemType.GOLD_NUGGET), 20, "I'll give you good money for a nugget of pure gold!"), sell_item_option(plain_item_id(ItemType.SAPHIRE), 30, "If you find a saphire I can make you real rich!"), DialogOptionData("\"Good bye\"", "cancel", None) ] dialog_data = DialogData("Gimli", PortraitIconSprite.VIKING, introduction, dialog_options) register_npc_dialog_data(npc_type, dialog_data) sprite_sheet = SpriteSheet("resources/graphics/enemy_sprite_sheet.png") original_sprite_size = (32, 32) scaled_sprite_size = (48, 48) indices_by_dir = { Direction.DOWN: [(0, 4), (1, 4), (2, 4)], Direction.LEFT: [(0, 5), (1, 5), (2, 5)], Direction.RIGHT: [(0, 6), (1, 6), (2, 6)], Direction.UP: [(0, 7), (1, 7), (2, 7)] } register_entity_sprite_map(sprite, sprite_sheet, original_sprite_size, scaled_sprite_size, indices_by_dir, (-8, -16)) register_portrait_icon_sprite_path( PortraitIconSprite.VIKING, 'resources/graphics/viking_portrait.png')
def register_dwarf_npc(): size = ( 30, 30 ) # Must not align perfectly with grid cell size (pathfinding issues) sprite = Sprite.NEUTRAL_NPC_DWARF npc_type = NpcType.NEUTRAL_DWARF movement_speed = 0.03 register_npc_data(npc_type, NpcData.neutral(sprite, size, movement_speed)) register_npc_behavior(npc_type, NpcMind) sprite_sheet = SpriteSheet("resources/graphics/enemy_sprite_sheet.png") original_sprite_size = (32, 32) scaled_sprite_size = (48, 48) indices_by_dir = { Direction.DOWN: [(0, 4), (1, 4), (2, 4)], Direction.LEFT: [(0, 5), (1, 5), (2, 5)], Direction.RIGHT: [(0, 6), (1, 6), (2, 6)], Direction.UP: [(0, 7), (1, 7), (2, 7)] } register_entity_sprite_map(sprite, sprite_sheet, original_sprite_size, scaled_sprite_size, indices_by_dir, (-8, -16)) register_quest_giver_dialog( npc_name="Gimli", npc_type=npc_type, icon_sprite=UI_ICON_SPRITE, icon_sprite_file_path='resources/graphics/viking_portrait.png', quest=Quest(QUEST_ID, "Corrupted orb", "Defeat the skeleton king and retrieve the magic orb."), quest_min_level=QUEST_MIN_LEVEL, quest_intro= "I've heard tales of a powerful skeleton mage that possesses a very rare magic orb. I think it's " "time that it finds itself a new owner!", boss_npc_type=NpcType.SKELETON_BOSS, quest_item_type=QUEST_ITEM_TYPE, custom_options=[ sell_item_option( plain_item_id(ItemType.GOLD_NUGGET), 20, "I'll give you good money for a nugget of pure gold!"), sell_item_option( plain_item_id(ItemType.SAPPHIRE), 30, "If you find a sapphire I can make you real rich!") ], dialog_before_quest= "Hello there. I'm always looking for treasure. If you find any, we might be able to strike a " "deal!", dialog_give_quest= "Hello there. I'm always looking for treasure. If you find any, we might be able to strike a " "deal!", dialog_during_quest="Hey! Any luck with the orb?", dialog_after_completed="Hi old friend! Got any more good stuff?", reward_item_id=lambda _: random_item_two_affixes(5))
def on_select(self, game_engine: GameEngine) -> Optional[str]: game_state = game_engine.game_state player_has_it = game_state.player_state.item_inventory.has_item_in_inventory( plain_item_id(self.quest_item_type)) if player_has_it: game_state.player_state.item_inventory.lose_item_from_inventory(plain_item_id(self.quest_item_type)) play_sound(SoundId.EVENT_COMPLETED_QUEST) game_state.player_state.complete_quest(self.quest) reward_item_id = self.reward_item_id(game_state) if reward_item_id: did_add_item = game_engine.try_add_item_to_inventory(reward_item_id) if not did_add_item: game_state.game_world.items_on_ground.append( create_item_on_ground(reward_item_id, game_state.game_world.player_entity.get_position())) return "Quest completed! Reward gained: " + reward_item_id.name return "Quest completed!" else: play_sound(SoundId.WARNING) return "You don't have that!"
def register_nomad_npc(): sprite = Sprite.NEUTRAL_NPC_NOMAD npc_type = NpcType.NEUTRAL_NOMAD register_npc_data( npc_type, NpcData.neutral(sprite=sprite, size=(30, 30), speed=0.03)) register_npc_behavior(npc_type, NpcMind) register_entity_sprite_map( sprite=sprite, sprite_sheet=SpriteSheet( "resources/graphics/enemy_sprite_sheet_3.png"), original_sprite_size=(32, 32), scaled_sprite_size=(48, 48), indices_by_dir={ Direction.DOWN: [(3, 0), (4, 0), (5, 0)], Direction.LEFT: [(3, 1), (4, 1), (5, 1)], Direction.RIGHT: [(3, 2), (4, 2), (5, 2)], Direction.UP: [(3, 3), (4, 3), (5, 3)] }, position_relative_to_entity=(-8, -16)) register_quest_giver_dialog( npc_name="Nomad", npc_type=NpcType.NEUTRAL_NOMAD, icon_sprite=PortraitIconSprite.NOMAD, icon_sprite_file_path='resources/graphics/nomad_portrait.png', quest=QUEST, quest_min_level=QUEST_MIN_LEVEL, quest_intro= "The red baron has caused us great trouble. Get rid of him and I'll be forever " "grateful! Oh, and please bring back anything interesting that he's carrying.", boss_npc_type=NpcType.WARRIOR_KING, quest_item_type=QUEST_ITEM_TYPE, custom_options=[ DialogOptionData("Receive blessing", "gain full health", HealAction()), DialogOptionData("Ask for advice", "see random hint", HintAction()) ], dialog_before_quest= "Greetings. I am here only to serve. Seek me out when you are wounded or need guidance!", dialog_give_quest= "Greetings. I am here only to serve. Seek me out when you are wounded or need guidance!", dialog_during_quest= "Greetings. I am here only to serve. Seek me out when you are wounded or need guidance!", dialog_after_completed= "Greetings. I am here only to serve. Seek me out when you are wounded or need guidance!", reward_item_id=lambda _: plain_item_id(ItemType.PORTAL_KEY))
def __init__(self, global_path_finder: GlobalPathFinder): super().__init__(global_path_finder, QUEST_ID, plain_item_id(QUEST_ITEM_TYPE), QUEST_MIN_LEVEL)
def on_hover(self, game_state: GameState, ui_view: GameUiView): _quest_on_hover(game_state, ui_view, self.boss_npc_type, plain_item_id(self.quest_item_type))
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))
def item_id_key(): # We defer calling this method as key item may not be registered yet otherwise return plain_item_id(ITEM_TYPE_KEY)
def __init__(self, global_path_finder: GlobalPathFinder): super().__init__(global_path_finder, QUEST_ID, plain_item_id(QUEST_ITEM_TYPE), QUEST_MIN_LEVEL) self.timer = PeriodicTimer(Millis(500)) self.quest_timer = PeriodicTimer(Millis(1000))