def _handle_gain_exp_events(self, gain_exp_events): did_level_up = False new_abilities: List[str] = [] did_unlock_new_talent = False for event in gain_exp_events: if isinstance(event, PlayerLeveledUp): did_level_up = True if isinstance(event, PlayerLearnedNewAbility): new_abilities.append(ABILITIES[event.ability_type].name) if isinstance(event, PlayerUnlockedNewTalent): did_unlock_new_talent = True if did_level_up: play_sound(SoundId.EVENT_PLAYER_LEVELED_UP) self.game_state.visual_effects.append( VisualCircle( (150, 150, 250), self.game_state.player_entity.get_center_position(), 9, 35, Millis(150), 2)) self.info_message.set_message( "You reached level " + str(self.game_state.player_state.level)) if new_abilities: allocate_input_keys_for_abilities( self.game_state.player_state.abilities) if len(new_abilities) == 1: self.info_message.enqueue_message("New ability: " + new_abilities[0]) elif len(new_abilities) > 1: self.info_message.enqueue_message("Gained several new abilities") if did_unlock_new_talent: self.talent_was_unlocked.notify(None) self.info_message.enqueue_message("You can pick a talent!")
def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): self.time_since_start += time_passed if not self.has_spawn_happened and self.time_since_start > DELAY / 2: self.has_spawn_happened = True game_state.game_world.visual_effects += create_teleport_effects(buffed_entity.get_center_position()) play_sound(SoundId.ABILITY_TELEPORT)
def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.remove_one() hero_center_pos = game_state.player_entity.get_center_position() distance = 80 affected_enemies = game_state.get_enemies_within_x_y_distance_of( distance, hero_center_pos) game_state.visual_effects.append( VisualRect((50, 50, 50), hero_center_pos, distance * 2, int(distance * 2.1), Millis(200), 2, None)) game_state.visual_effects.append( VisualRect((150, 150, 0), hero_center_pos, distance, distance * 2, Millis(150), 3, None)) game_state.visual_effects.append( VisualRect((250, 250, 0), hero_center_pos, distance, distance * 2, Millis(100), 4, None)) for enemy in affected_enemies: damage: float = MIN_DMG + random.random() * (MAX_DMG - MIN_DMG) deal_player_damage_to_enemy(game_state, enemy, damage, DamageType.PHYSICAL) enemy.gain_buff_effect(get_buff_effect(STUNNED_BY_STOMP), STUN_DURATION) game_state.player_state.gain_buff_effect( get_buff_effect(BuffType.RECOVERING_AFTER_ABILITY), Millis(300)) play_sound(SoundId.ABILITY_STOMP_HIT) game_state.camera_shake = CameraShake(Millis(50), Millis(200), 12)
def on_select(self, game_state: GameState): if game_state.player_state.item_inventory.has_item_in_inventory(ITEM_TYPE_KEY): play_sound(SoundId.EVENT_COMPLETED_QUEST) game_state.player_state.complete_quest(QUEST) else: play_sound(SoundId.WARNING) return "You don't have that!"
def update(self, npc: NonPlayerCharacter, game_state: GameState, time_passed: Millis): self._time_since_attack += time_passed if self._time_since_attack > self._attack_interval: self._time_since_attack = 0 self._update_attack_interval() directions_to_player = get_directions_to_position( npc.world_entity, game_state.game_world.player_entity.get_position()) new_direction = directions_to_player[0] if random.random( ) < self._chance_to_shoot_other_direction and directions_to_player[ 1] is not None: new_direction = directions_to_player[1] npc.world_entity.direction = new_direction npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, self._projectile_size), npc.world_entity.direction, distance_from_enemy) projectile = self._create_projectile(projectile_pos, npc.world_entity.direction) game_state.game_world.projectile_entities.append(projectile) play_sound(self._sound_id)
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return self._time_since_attack += time_passed self._time_since_updated_path += time_passed self._time_since_reevaluated += time_passed enemy_entity = npc.world_entity target: EnemyTarget = get_target(enemy_entity, game_state) if self._time_since_updated_path > self._update_path_interval: self._time_since_updated_path = 0 if not is_player_invisible: self.pathfinder.update_path_towards_target( enemy_entity, game_state, target.entity) new_next_waypoint = self.pathfinder.get_next_waypoint_along_path( enemy_entity) should_update_waypoint = self.next_waypoint != new_next_waypoint if self._time_since_reevaluated > self._reevaluate_next_waypoint_direction_interval: self._time_since_reevaluated = 0 should_update_waypoint = True if should_update_waypoint: self.next_waypoint = new_next_waypoint if self.next_waypoint: direction = self.pathfinder.get_dir_towards_considering_collisions( game_state, enemy_entity, self.next_waypoint) _move_in_dir(enemy_entity, direction) else: enemy_entity.set_not_moving() if self._time_since_attack > self._attack_interval: if not is_player_invisible: enemy_position = enemy_entity.get_center_position() target_center_pos = target.entity.get_center_position() if is_x_and_y_within_distance(enemy_position, target_center_pos, 200): self._time_since_attack = 0 self.randomize_attack_interval() play_sound(SoundId.ENEMY_ATTACK_ICE_WITCH) damage = random.randint(DAMAGE_MIN, DAMAGE_MAX) deal_npc_damage(damage, DamageType.MAGIC, game_state, enemy_entity, npc, target) game_state.game_world.visual_effects += [ (VisualLine((100, 100, 200), enemy_position, target_center_pos, Millis(120), 3)), (VisualLine((150, 150, 250), enemy_position, target_center_pos, Millis(240), 2)) ] chance_to_resist_slow = game_state.player_state.get_effective_movement_impairing_resist_chance( ) # TODO It's error-prone that we have to check this for every negative debuff that can slow player if random.random() > chance_to_resist_slow: game_state.player_state.gain_buff_effect( get_buff_effect(SLOW_BUFF_TYPE), Millis(1500))
def on_select(self, game_state: GameState) -> Optional[str]: if not game_state.player_state.health_resource.is_at_max(): health_gained = game_state.player_state.health_resource.gain_to_max() game_state.visual_effects.append(create_visual_healing_text(game_state.player_entity, health_gained)) play_sound(SoundId.CONSUMABLE_POTION) return "You feel healthy again!" play_sound(SoundId.WARNING) return "Already at full health!"
def on_select(self, game_state: GameState): if game_state.player_state.item_inventory.has_item_in_inventory( ItemType.KEY): play_sound(SoundId.EVENT_COMPLETED_QUEST) game_state.player_state.has_finished_main_quest = True else: play_sound(SoundId.WARNING) return "You don't have that!"
def _try_pick_up_item_from_ground(self, item: ItemOnGround): item_name = item.item_id.name did_add_item = self.try_add_item_to_inventory(item.item_id) if did_add_item: play_sound(SoundId.EVENT_PICKED_UP) self.game_state.game_world.items_on_ground.remove(item) self.info_message.set_message("You picked up " + item_name) else: self.info_message.set_message("No space for " + item_name)
def _save_game(self): play_sound(SoundId.EVENT_SAVED_GAME) filename = self.save_file_handler.save_to_file( self.game_state, self.character_file, self.total_time_played_on_character) if self.character_file is None: # This is relevant when saving a character for the first time. If we didn't update the field, we would # be creating a new file everytime we saved. self.character_file = filename self.ui_state.set_message("Game was saved.")
def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): self.time_since_start += time_passed if not self.has_teleport_happened and self.time_since_start > PORTAL_DELAY / 2: self.has_teleport_happened = True game_state.warp_points = [] game_state.player_entity.set_position(self.destination) game_state.visual_effects += create_teleport_effects(buffed_entity.get_center_position()) play_sound(SoundId.WARP)
def handle_event(self, event: EngineEvent) -> Optional[SceneTransition]: if event == EngineEvent.PLAYER_DIED: self.game_state.player_entity.set_position(self.game_state.player_spawn_position) self.game_state.player_state.health_resource.set_to_partial_of_max(0.5) self.game_state.player_state.lose_exp_from_death() self.game_state.player_state.force_cancel_all_buffs() self.info_message.set_message("Lost exp from dying") play_sound(SoundId.EVENT_PLAYER_DIED) self.game_state.player_state.gain_buff_effect(get_buff_effect(BuffType.BEING_SPAWNED), Millis(1000)) return None
def _on_click_toggle(self, clicked_toggle: ToggleButton): play_sound(SoundId.UI_TOGGLE) if clicked_toggle.is_open: self.enabled_toggle.close() self.enabled_toggle = None else: if self.enabled_toggle is not None: self.enabled_toggle.close() self.enabled_toggle = clicked_toggle self.enabled_toggle.open()
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, _is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return self._summon_trait.update(npc, game_state, time_passed) self._random_walk_trait.update(npc, game_state, time_passed) self._time_since_healing += time_passed self._time_since_shoot += time_passed if self._time_since_healing > self._healing_cooldown: self._time_since_healing = 0 self._healing_cooldown = self._random_healing_cooldown() necro_center_pos = npc.world_entity.get_center_position() nearby_hurt_enemies = [ e for e in game_state.game_world.non_player_characters if e.is_enemy and is_x_and_y_within_distance( necro_center_pos, e.world_entity.get_center_position(), 200) and e != npc and not e.health_resource.is_at_max() ] if nearby_hurt_enemies: healing_target = nearby_hurt_enemies[0] healing_target.health_resource.gain(5) healing_target_pos = healing_target.world_entity.get_center_position( ) visual_line = VisualLine((80, 200, 150), necro_center_pos, healing_target_pos, Millis(350), 3) game_state.game_world.visual_effects.append(visual_line) play_sound(SoundId.ENEMY_NECROMANCER_HEAL) if self._time_since_shoot > self._shoot_cooldown: self._time_since_shoot = 0 self._shoot_cooldown = self._random_shoot_cooldown() npc.world_entity.direction = get_directions_to_position( npc.world_entity, player_entity.get_position())[0] npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.2 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction, projectile_speed) projectile = Projectile( projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.game_world.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_ATTACK_NECRO)
def apply_enemy_collision(self, npc: NonPlayerCharacter, game_state: GameState, projectile: Projectile): damage_was_dealt = deal_player_damage_to_enemy(game_state, npc, 1, DamageType.MAGIC) if damage_was_dealt: npc.gain_buff_effect(get_buff_effect(BUFF_TYPE), DEBUFF_DURATION) victim_center_pos = npc.world_entity.get_center_position() visual_effect_pos = (victim_center_pos[0] - ENTANGLING_ROOTS_SIZE[0] // 2, victim_center_pos[1] - ENTANGLING_ROOTS_SIZE[1] // 2) debuff_visual_effect = VisualSprite(Sprite.DECORATION_ENTANGLING_ROOTS_EFFECT, visual_effect_pos, DEBUFF_DURATION, npc.world_entity) game_state.game_world.visual_effects.append(debuff_visual_effect) play_sound(SoundId.ABILITY_ENTANGLING_ROOTS_HIT) projectile.has_collided_and_should_be_removed = True
def _change_option(self, delta: int): num_options = len(self._saved_characters) + 1 self._selected_option_index = (self._selected_option_index + delta) % num_options if self._first_shown_option_index + NUM_SHOWN_SAVE_FILES <= self._selected_option_index \ < len(self._saved_characters): self._first_shown_option_index = self._selected_option_index - NUM_SHOWN_SAVE_FILES + 1 elif self._selected_option_index < self._first_shown_option_index: self._first_shown_option_index = self._selected_option_index play_sound(SoundId.DIALOG)
def on_select(self, game_state: GameState) -> Optional[str]: player_has_it = game_state.player_state.item_inventory.has_item_in_inventory( self.item_id) if player_has_it: game_state.player_state.item_inventory.lose_item_from_inventory( self.item_id) game_state.player_state.modify_money(self.price) play_sound(SoundId.EVENT_SOLD_SOMETHING) return "Sold " + self.name else: play_sound(SoundId.WARNING) return "You don't have that!"
def on_select(self, game_engine: Any) -> Optional[str]: game_state = game_engine.game_state player_state = game_state.player_state can_afford = player_state.money >= self.price if not can_afford: play_sound(SoundId.WARNING) return "Not enough gold!" player_state.modify_money(-self.price) reset_talents(game_state) play_sound(SoundId.EVENT_RESET_TALENT) return "Talents reset"
def handle_being_close_to_portal(self, portal: Portal): # When finding a new portal out on the map, it's enough to walk close to it, to activate its sibling if portal.is_enabled: destination_portal = self.game_state.game_world.get_portal_with_id( portal.leads_to) if not destination_portal.is_enabled: play_sound(SoundId.EVENT_PORTAL_ACTIVATED) self.info_message.set_message("Portal was activated") self.activate_portal(destination_portal, portal.world_entity.sprite) self.game_state.game_world.visual_effects += create_teleport_effects( portal.world_entity.get_center_position())
def _try_pick_up_item_from_ground(self, item: ItemOnGround): item_effect = get_item_effect(item.item_type) item_data = ITEMS[item.item_type] item_equipment_category = item_data.item_equipment_category did_add_item = try_add_item_to_inventory(self.game_state, item_effect, item_equipment_category) if did_add_item: play_sound(SoundId.EVENT_PICKED_UP) self.game_state.items_on_ground.remove(item) self.info_message.set_message("You picked up " + item_data.name) else: self.info_message.set_message("No space for " + item_data.name)
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): self._time_since_attack += time_passed self._time_since_updated_path += time_passed self._time_since_reevaluated += time_passed enemy_entity = npc.world_entity target: EnemyTarget = get_target(enemy_entity, game_state) if self._time_since_updated_path > self._update_path_interval: self._time_since_updated_path = 0 if not is_player_invisible: self.pathfinder.update_path_towards_target(enemy_entity, game_state, target.entity) new_next_waypoint = self.pathfinder.get_next_waypoint_along_path(enemy_entity) should_update_waypoint = self.next_waypoint != new_next_waypoint if self._time_since_reevaluated > self._reevaluate_next_waypoint_direction_interval: self._time_since_reevaluated = 0 should_update_waypoint = True if should_update_waypoint: self.next_waypoint = new_next_waypoint if self.next_waypoint: direction = self.pathfinder.get_dir_towards_considering_collisions( game_state, enemy_entity, self.next_waypoint) if random.random() < 0.5 and direction: direction = random.choice(get_perpendicular_directions(direction)) _move_in_dir(enemy_entity, direction) else: enemy_entity.set_not_moving() if self._time_since_attack > self._attack_interval: self._time_since_attack = 0 self._update_attack_interval() directions_to_player = get_directions_to_position(npc.world_entity, player_entity.get_position()) new_direction = directions_to_player[0] if random.random() < 0.3 and directions_to_player[1] is not None: new_direction = directions_to_player[1] npc.world_entity.direction = new_direction npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.11 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, PROJECTILE_SPRITE, npc.world_entity.direction, projectile_speed) projectile = Projectile(projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_ATTACK_GOBLIN_WARLOCK)
def deal_damage_to_player(game_state: GameState, base_amount: float, damage_type: DamageType, npc_attacker: Optional[NonPlayerCharacter]): player_state = game_state.player_state if npc_attacker: player_state.notify_about_event(PlayerWasAttackedEvent(npc_attacker), game_state) damage_reduction = 0 # Armor only reduces physical damage if damage_type == DamageType.PHYSICAL: dodge_chance = player_state.get_effective_dodge_chance() block_chance = player_state.get_effective_block_chance() if random.random() < dodge_chance: game_state.game_world.visual_effects.append( create_visual_dodge_text(game_state.game_world.player_entity)) play_sound(SoundId.ENEMY_ATTACK_WAS_DODGED) player_state.notify_about_event(PlayerDodgedEvent(npc_attacker), game_state) return elif random.random() < block_chance: if player_state.block_damage_reduction > 0: game_state.game_world.visual_effects.append( create_visual_block_text( game_state.game_world.player_entity)) damage_reduction += player_state.block_damage_reduction player_state.notify_about_event(PlayerBlockedEvent(npc_attacker), game_state) # Armor has a random element to it. Example: 5 armor absorbs 0-5 damage damage_reduction += random.randint(0, player_state.get_effective_armor()) elif damage_type == DamageType.MAGIC: resist_chance = player_state.get_effective_magic_resist_chance() if random.random() < resist_chance: game_state.game_world.visual_effects.append( create_visual_resist_text(game_state.game_world.player_entity)) play_sound(SoundId.MAGIC_DAMAGE_WAS_RESISTED) return amount = max(0.0, base_amount - damage_reduction) health_lost_integer = player_state.health_resource.lose(amount) if health_lost_integer > 0: game_state.game_world.visual_effects.append( create_visual_damage_text(game_state.game_world.player_entity, health_lost_integer)) play_sound(SoundId.ENEMY_ATTACK) if random.random() < 0.3: play_sound(SoundId.PLAYER_PAIN) player_state.notify_about_event( PlayerLostHealthEvent(health_lost_integer, npc_attacker), game_state) else: play_sound(SoundId.ENEMY_ATTACK_WAS_BLOCKED)
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, _is_player_invisible: bool, time_passed: Millis): self._time_since_decision += time_passed self._time_since_healing += time_passed self._time_since_shoot += time_passed if self._time_since_healing > self._healing_cooldown: self._time_since_healing = 0 self._healing_cooldown = self._random_healing_cooldown() if not npc.health_resource.is_at_max(): healing_amount = random.randint(10, 20) npc.health_resource.gain(healing_amount) circle_effect = VisualCircle( (80, 200, 150), npc.world_entity.get_center_position(), 30, 50, Millis(350), 3) game_state.visual_effects.append(circle_effect) number_effect = create_visual_healing_text( npc.world_entity, healing_amount) game_state.visual_effects.append(number_effect) play_sound(SoundId.ENEMY_SKELETON_MAGE_HEAL) if self._time_since_shoot > self._shoot_cooldown: self._time_since_shoot = 0 self._shoot_cooldown = self._random_shoot_cooldown() npc.world_entity.direction = get_directions_to_position( npc.world_entity, player_entity.get_position())[0] npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.2 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction, projectile_speed) projectile = Projectile( projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_ATTACK_SKELETON_MAGE) if self._time_since_decision > self._decision_interval: self._time_since_decision = 0 if random.random() < 0.2: direction = random_direction() npc.world_entity.set_moving_in_dir(direction) else: npc.world_entity.set_not_moving()
def apply_enemy_collision(self, npc: NonPlayerCharacter, game_state: GameState, projectile: Projectile): damage_amount: float = MIN_DMG + random.random() * (MAX_DMG - MIN_DMG) deal_player_damage_to_enemy(game_state, npc, damage_amount, DamageType.MAGIC) _create_visual_splash(npc.world_entity.get_center_position(), game_state) has_burn_upgrade = game_state.player_state.has_upgrade( HeroUpgradeId.ABILITY_FIREBALL_BURN) if has_burn_upgrade: npc.gain_buff_effect(get_buff_effect(BUFF_TYPE), FIREBALL_TALENT_BURN_DURATION) play_sound(SoundId.ABILITY_FIREBALL_HIT) projectile.has_collided_and_should_be_removed = True
def _try_pick_up_consumable_from_ground(self, consumable: ConsumableOnGround): # TODO move some logic into ConsumableInventory class has_space = self.game_state.player_state.consumable_inventory.has_space_for_more( ) consumable_type = consumable.consumable_type consumable_name = CONSUMABLES[consumable_type].name if has_space: self.game_state.player_state.consumable_inventory.add_consumable( consumable_type) self.info_message.set_message("You picked up " + consumable_name) play_sound(SoundId.EVENT_PICKED_UP) self.game_state.consumables_on_ground.remove(consumable) else: self.info_message.set_message("No space for " + consumable_name)
def _apply_ability(game_state: GameState) -> AbilityResult: player_entity = game_state.player_entity rect_w = 28 slash_center_pos = translate_in_direction( player_entity.get_center_position(), player_entity.direction, rect_w / 2 + PLAYER_ENTITY_SIZE[0] * 0.25) slash_rect = Rect(int(slash_center_pos[0] - rect_w / 2), int(slash_center_pos[1] - rect_w / 2), rect_w, rect_w) affected_enemies = game_state.get_enemy_intersecting_rect(slash_rect) is_stealthed = game_state.player_state.has_active_buff(BuffType.STEALTHING) if is_stealthed: play_sound(SoundId.ABILITY_SHIV_STEALTHED) else: play_sound(SoundId.ABILITY_SHIV) for enemy in affected_enemies: damage: float = MIN_DMG + random.random() * (MAX_DMG - MIN_DMG) # Note: Dependency on other ability 'stealth' if is_stealthed: # Talent: increase the damage bonus that Shiv gets from being used while stealthing has_damage_upgrade = game_state.player_state.has_upgrade( HeroUpgradeId.ABILITY_SHIV_SNEAK_BONUS_DAMAGE) damage *= SHIV_UPGRADED_STEALTH_DAMAGE_MULTIPLIER if has_damage_upgrade else SHIV_STEALTH_DAMAGE_MULTIPLIER game_state.camera_shake = CameraShake(Millis(50), Millis(150), 4) else: # Talent: if attacking an enemy that's at 100% health while not stealthing, deal bonus damage has_damage_upgrade = game_state.player_state.has_upgrade( HeroUpgradeId.ABILITY_SHIV_FULL_HEALTH_BONUS_DAMAGE) if has_damage_upgrade and enemy.health_resource.is_at_max(): damage *= SHIV_TALENT_FULL_HEALTH_DAMAGE_MULTIPLIER deal_player_damage_to_enemy(game_state, enemy, damage, DamageType.PHYSICAL, visual_emphasis=is_stealthed) break game_state.visual_effects.append( VisualRect((150, 150, 75), slash_center_pos, rect_w, int(rect_w * 0.7), Millis(200), 2, None)) game_state.visual_effects.append( VisualCross((100, 100, 70), slash_center_pos, 6, Millis(100), 2)) game_state.player_state.gain_buff_effect( get_buff_effect(BuffType.RECOVERING_AFTER_ABILITY), Millis(250)) return AbilityWasUsedSuccessfully()
def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis) -> Optional[bool]: self.time_since_start += time_passed charger_center_pos = buffed_entity.get_center_position() if self.graphics_timer.update_and_check_if_ready(time_passed): visual_circle = VisualCircle((250, 250, 250), charger_center_pos, 15, 25, Millis(120), 2, None) game_state.visual_effects.append(visual_circle) rect_w = 32 # NOTE: We assume that this ability is used by this specific hero hero_entity_size = HEROES[HeroId.WARRIOR].entity_size impact_pos = translate_in_direction( charger_center_pos, buffed_entity.direction, rect_w / 2 + hero_entity_size[0] / 2) impact_rect = Rect(int(impact_pos[0] - rect_w / 2), int(impact_pos[1] - rect_w / 2), rect_w, rect_w) affected_enemies = game_state.get_enemy_intersecting_rect(impact_rect) for enemy in affected_enemies: visual_impact_pos = get_middle_point(charger_center_pos, enemy.world_entity.get_center_position()) damage = MIN_DMG # Talent: Apply damage bonus even if using charge in melee range has_melee_upgrade = game_state.player_state.has_upgrade(HeroUpgradeId.ABILITY_CHARGE_MELEE) damage_increased = self.time_since_start > float(CHARGE_DURATION) * 0.3 or has_melee_upgrade if damage_increased: # TODO Stun target as a bonus here damage = MAX_DMG deal_player_damage_to_enemy(game_state, enemy, damage, DamageType.PHYSICAL, visual_emphasis=damage_increased) game_state.visual_effects.append( VisualRect((250, 170, 0), visual_impact_pos, 45, 25, IMPACT_STUN_DURATION, 2, None)) game_state.visual_effects.append( VisualRect((150, 0, 0), visual_impact_pos, 35, 20, IMPACT_STUN_DURATION, 2, None)) game_state.player_state.gain_buff_effect(get_buff_effect(BUFF_TYPE_STUNNED), IMPACT_STUN_DURATION) enemy.gain_buff_effect(get_buff_effect(BUFF_TYPE_STUNNED), IMPACT_STUN_DURATION) game_state.camera_shake = CameraShake(Millis(50), Millis(150), 12) play_sound(SoundId.ABILITY_CHARGE_HIT) has_stomp_cooldown_upgrade = game_state.player_state.has_upgrade( HeroUpgradeId.ABILITY_CHARGE_RESET_STOMP_COOLDOWN) if has_stomp_cooldown_upgrade: game_state.player_state.set_ability_cooldown_to_zero(AbilityType.STOMP) # The buff should end upon impact return True return False
def update(self, npc: NonPlayerCharacter, game_state: GameState, time_passed: Millis): self._time_since_summoning += time_passed if self._time_since_summoning > self._summoning_cooldown: necro_center_pos = npc.world_entity.get_center_position() self._time_since_summoning = 0 self._alive_summons = [ summon for summon in self._alive_summons if summon in game_state.game_world.non_player_characters ] if len(self._alive_summons) < self._max_summons: relative_pos_from_summoner = (random.randint(-150, 150), random.randint(-150, 150)) summon_center_pos = sum_of_vectors(necro_center_pos, relative_pos_from_summoner) summon_type = random.choice(self._summon_npc_types) summon_size = NON_PLAYER_CHARACTERS[summon_type].size summon_pos = game_state.game_world.get_within_world( get_position_from_center_position(summon_center_pos, summon_size), summon_size) summon_enemy = self._create_npc(summon_type, summon_pos) is_wall_blocking = game_state.game_world.walls_state.does_rect_intersect_with_wall( rect_from_corners(necro_center_pos, summon_center_pos)) is_position_blocked = game_state.game_world.would_entity_collide_if_new_pos( summon_enemy.world_entity, summon_pos) if not is_wall_blocking and not is_position_blocked: self._summoning_cooldown = self._random_summoning_cooldown( ) game_state.game_world.add_non_player_character( summon_enemy) self._alive_summons.append(summon_enemy) game_state.game_world.visual_effects.append( VisualCircle((80, 150, 100), necro_center_pos, 40, 70, Millis(120), 3)) game_state.game_world.visual_effects.append( VisualCircle((80, 150, 100), summon_center_pos, 40, 70, Millis(120), 3)) play_sound(SoundId.ENEMY_NECROMANCER_SUMMON) else: # Failed to summon, so try again without waiting full duration self._summoning_cooldown = 500 else: self._summoning_cooldown = self._random_summoning_cooldown()
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 control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return super().control_npc(game_state, npc, player_entity, is_player_invisible, time_passed) self._time_since_state_change += time_passed if self._state == State.BASE: if self._time_since_state_change > NpcMind.STATE_DURATION_BASE: self._time_since_state_change -= NpcMind.STATE_DURATION_BASE self._state = State.FIRING npc.gain_buff_effect(get_buff_effect(BUFF_STUNNED), Millis(NpcMind.STATE_DURATION_FIRING)) return elif self._state == State.FIRING: if self._time_since_state_change > NpcMind.STATE_DURATION_FIRING: self._time_since_state_change -= NpcMind.STATE_DURATION_FIRING self._state = State.BASE return self._time_since_fired += time_passed if self._time_since_fired > NpcMind.FIRE_COOLDOWN: self._time_since_fired -= NpcMind.FIRE_COOLDOWN directions_to_player = get_directions_to_position(npc.world_entity, player_entity.get_position()) new_direction = directions_to_player[0] if random.random() < 0.1 and directions_to_player[1] is not None: new_direction = directions_to_player[1] npc.world_entity.direction = new_direction npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.3 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction, projectile_speed) projectile = Projectile(projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.game_world.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_MAGIC_SKELETON_BOSS)