class QuestGiverNpcMind(AbstractNpcMind): def __init__(self, global_path_finder: GlobalPathFinder, quest_id: QuestId, quest_item_id: ItemId, quest_min_level: int): super().__init__(global_path_finder) self.timer = PeriodicTimer(Millis(500)) self.quest_timer = PeriodicTimer(Millis(1000)) self.quest_id = quest_id self.quest_item_id = quest_item_id self.quest_min_level = quest_min_level def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if self.quest_timer.update_and_check_if_ready(time_passed): player_state = game_state.player_state if player_state.has_quest(self.quest_id): if player_state.item_inventory.has_item_in_inventory(self.quest_item_id): npc.quest_giver_state = QuestGiverState.CAN_COMPLETE_QUEST else: npc.quest_giver_state = QuestGiverState.WAITING_FOR_PLAYER elif player_state.has_completed_quest(self.quest_id): npc.quest_giver_state = None elif player_state.level >= self.quest_min_level: npc.quest_giver_state = QuestGiverState.CAN_GIVE_NEW_QUEST else: npc.quest_giver_state = None if self.timer.update_and_check_if_ready(time_passed): if random.random() < 0.8: npc.world_entity.set_not_moving() else: direction = random.choice(get_all_directions()) npc.world_entity.set_moving_in_dir(direction)
class DebuffedByGoatsRing(AbstractBuffEffect): def __init__(self): self.dmg_timer = PeriodicTimer(Millis(1000)) self.graphics_timer = PeriodicTimer(Millis(400)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.dmg_timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, 1, DamageType.MAGIC, damage_source=DAMAGE_SOURCE) if self.graphics_timer.update_and_check_if_ready(time_passed): position = buffed_entity.get_center_position() visual_effect1 = VisualCircle((0, 100, 40), position, 9, 16, Millis(400), 2, buffed_entity) visual_effect2 = VisualCircle((0, 180, 90), position, 9, 16, Millis(500), 2, buffed_entity) game_state.visual_effects.append(visual_effect1) game_state.visual_effects.append(visual_effect2) def get_buff_type(self): return BUFF_TYPE
class Channeling(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(CHANNEL_PROJECTILE_INTERVAL) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.add_one() game_state.player_entity.set_not_moving() game_state.camera_shake = CameraShake(Millis(50), CHANNEL_DURATION, 5) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): player_center_position = game_state.player_entity.get_center_position() projectile_pos = get_position_from_center_position(player_center_position, PROJECTILE_SIZE) entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.PROJECTILE_PLAYER_ARCANE_FIRE, game_state.player_entity.direction, PROJECTILE_SPEED) projectile = Projectile(entity, create_projectile_controller(ProjectileType.PLAYER_ARCANE_FIRE)) game_state.projectile_entities.append(projectile) game_state.visual_effects.append(VisualRect((250, 0, 250), player_center_position, 45, 60, Millis(250), 1)) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.remove_one() def get_buff_type(self): return BuffType.CHANNELING_ARCANE_FIRE
class DamagedByInfusedDagger(AbstractBuffEffect): def __init__(self, should_stun: bool): self.timer = PeriodicTimer(DAMAGE_TICK_INTERVAL) self.should_stun = should_stun def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): if self.should_stun: buffed_npc.stun_status.add_one() buffed_entity.set_not_moving() game_state.game_world.visual_effects.append( create_visual_stun_text(buffed_entity)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, DAMAGE_PER_TICK, DamageType.PHYSICAL) if self.should_stun: effect_position = buffed_entity.get_center_position() game_state.game_world.visual_effects.append( VisualRect((250, 250, 50), effect_position, 30, 40, Millis(100), 1, buffed_entity)) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): if self.should_stun: buffed_npc.stun_status.remove_one() def get_buff_type(self): return DEBUFF
class Rooted(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(DEBUFF_DAMAGE_INTERVAL) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_npc.stun_status.add_one() game_state.visual_effects.append( create_visual_stun_text(buffed_entity)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, 1, DamageType.MAGIC) game_state.visual_effects.append( VisualCircle((0, 150, 0), buffed_entity.get_center_position(), 30, 55, Millis(150), 2, buffed_entity)) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_npc.stun_status.remove_one() def get_buff_type(self): return BUFF_TYPE
class BloodLust(StatModifyingBuffEffect): def __init__(self): super().__init__(BUFF_TYPE, {HeroStat.LIFE_STEAL: LIFE_STEAL_BONUS_RATIO, HeroStat.MOVEMENT_SPEED: SPEED_BONUS}) self.timer = PeriodicTimer(Millis(250)) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): super().apply_start_effect(game_state, buffed_entity, buffed_npc) sword_slash_data = ABILITIES[AbilityType.SWORD_SLASH] sword_slash_data.cooldown -= SWORD_SLASH_CD_BONUS def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): visual_effect = VisualCircle( (250, 0, 0,), buffed_entity.get_center_position(), 25, 30, Millis(350), 1, buffed_entity) game_state.visual_effects.append(visual_effect) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): super().apply_end_effect(game_state, buffed_entity, buffed_npc) sword_slash_data = ABILITIES[AbilityType.SWORD_SLASH] sword_slash_data.cooldown += SWORD_SLASH_CD_BONUS def buff_handle_event(self, event: Event) -> Optional[BuffEventOutcome]: if isinstance(event, EnemyDiedEvent): if has_blood_lust_duration_increase_upgrade: duration_increase = BLOODLUST_UPGRADED_INCREASED_DURATION_FROM_KILL else: duration_increase = BLOODLUST_INCREASED_DURATION_FROM_KILL return BuffEventOutcome.change_remaining_duration(duration_increase)
class ProjectileController(AbstractProjectileController): def __init__(self): super().__init__(2000) self._color = (50, 180, 50) self._timer = PeriodicTimer(Millis(100)) self._min_damage = 8 self._max_damage = 12 def notify_time_passed(self, game_state: GameState, projectile: Projectile, time_passed: Millis): super().notify_time_passed(game_state, projectile, time_passed) if self._timer.update_and_check_if_ready(time_passed): head = VisualCircle(self._color, projectile.world_entity.get_center_position(), 15, 15, Millis(150), 0, projectile.world_entity) tail = VisualCircle(self._color, projectile.world_entity.get_center_position(), 15, 1, Millis(400), 0) game_state.visual_effects += [head, tail] def apply_player_collision(self, game_state: GameState, projectile: Projectile): damage = random.randint(self._min_damage, self._max_damage) deal_damage_to_player(game_state, damage, DamageType.MAGIC, None) game_state.visual_effects.append(VisualCircle(self._color, game_state.player_entity.get_center_position(), 25, 50, Millis(100), 0)) projectile.has_collided_and_should_be_removed = True def apply_player_summon_collision(self, npc: NonPlayerCharacter, game_state: GameState, projectile: Projectile): damage = random.randint(self._min_damage, self._max_damage) deal_npc_damage_to_npc(game_state, npc, damage) game_state.visual_effects.append( VisualCircle(self._color, npc.world_entity.get_center_position(), 25, 50, Millis(100), 0)) projectile.has_collided_and_should_be_removed = True def apply_wall_collision(self, game_state: GameState, projectile: Projectile): game_state.visual_effects.append( VisualCircle(self._color, projectile.world_entity.get_center_position(), 13, 26, Millis(100), 0)) projectile.has_collided_and_should_be_removed = True
class Invisibility(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(Millis(320)) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.is_invisible = True def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): game_state.visual_effects.append( VisualRect((0, 0, 250), game_state.player_entity.get_center_position(), 45, 60, Millis(400), 1, game_state.player_entity)) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.is_invisible = False def get_buff_type(self): return BUFF_TYPE
class BuffEffect(AbstractBuffEffect): def __init__(self): self.graphics_timer = PeriodicTimer(Millis(500)) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_entity.add_to_speed_multiplier(SPRINT_SPEED_BONUS) self.create_sprint_visual_effect(buffed_entity, game_state) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.graphics_timer.update_and_check_if_ready(time_passed): self.create_sprint_visual_effect(buffed_entity, game_state) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_entity.add_to_speed_multiplier(-SPRINT_SPEED_BONUS) def get_buff_type(self): return BUFF_SPRINT @staticmethod def create_sprint_visual_effect(buffed_entity: WorldEntity, game_state: GameState): game_state.game_world.visual_effects.append( VisualCircle((150, 50, 0), buffed_entity.get_center_position(), 20, 22, Millis(250), 2, buffed_entity))
class DebuffedByFreezingGauntlet(AbstractBuffEffect): def __init__(self): self.graphics_timer = PeriodicTimer(Millis(400)) def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_entity.add_to_speed_multiplier(-SLOW_AMOUNT) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): buffed_entity.add_to_speed_multiplier(SLOW_AMOUNT) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.graphics_timer.update_and_check_if_ready(time_passed): position = buffed_entity.get_center_position() visual_effect1 = VisualCircle((0, 40, 100), position, 9, 16, Millis(400), 2, buffed_entity) visual_effect2 = VisualCircle((0, 90, 180), position, 9, 16, Millis(500), 2, buffed_entity) game_state.visual_effects.append(visual_effect1) game_state.visual_effects.append(visual_effect2) def get_buff_type(self): return BUFF_TYPE
class ItemEffect(AbstractItemEffect): def __init__(self): self.timer = PeriodicTimer(Millis(5000)) def apply_middle_effect(self, game_state: GameState, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): strike_enemies(game_state, 1)
class ItemEffect(AbstractItemEffect): def __init__(self, item_type: ItemType): super().__init__(item_type) self.timer = PeriodicTimer(Millis(5000)) self.min_dmg = 1 self.max_dmg = 3 def apply_middle_effect(self, game_state: GameState, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): player_entity = game_state.player_entity player_center_position = player_entity.get_center_position() close_enemies = game_state.get_enemies_within_x_y_distance_of( 140, player_center_position) if close_enemies: damage_amount: float = self.min_dmg + random.random() * ( self.max_dmg - self.min_dmg) deal_player_damage_to_enemy(game_state, close_enemies[0], damage_amount, DamageType.MAGIC) enemy_center_position = close_enemies[ 0].world_entity.get_center_position() game_state.visual_effects.append( VisualCircle((250, 250, 0), player_center_position, 50, 140, Millis(100), 1, player_entity)) game_state.visual_effects.append( VisualLine((250, 250, 0), player_center_position, enemy_center_position, Millis(80), 3)) def get_description(self): return [ "Periodically deals " + str(self.min_dmg) + "-" + str(self.max_dmg) + " magic damage to nearby enemies" ]
class ProjectileController(AbstractProjectileController): def __init__(self): super().__init__(PROJECTILE_DURATION) self.damage_timer = PeriodicTimer(PROJECTILE_DAMAGE_INTERVAL) self.direction_change_timer = PeriodicTimer(Millis(250)) self._relative_direction = 0 self._rotation_motion = random.choice([-1, 1]) def notify_time_passed(self, game_state: GameState, projectile: Projectile, time_passed: Millis): super().notify_time_passed(game_state, projectile, time_passed) projectile_entity = projectile.world_entity if self.damage_timer.update_and_check_if_ready(time_passed): for enemy in game_state.game_world.get_enemy_intersecting_with( projectile_entity): damage_amount = 1 damage_was_dealt = deal_player_damage_to_enemy( game_state, enemy, damage_amount, DamageType.MAGIC) if damage_was_dealt: has_stun_upgrade = game_state.player_state.has_upgrade( HeroUpgradeId.ABILITY_WHIRLWIND_STUN) if has_stun_upgrade and random.random() < 0.2: enemy.gain_buff_effect(get_buff_effect(BUFF_TYPE), WHIRLWIND_TALENT_STUN_DURATION) if self.direction_change_timer.update_and_check_if_ready(time_passed): should_rotate = True # keep going straight ahead sometimes if self._relative_direction == 0 and random.random() < 0.5: should_rotate = False if should_rotate: if self._rotation_motion == 1: projectile_entity.rotate_right() self._relative_direction += 90 elif self._rotation_motion == -1: projectile_entity.rotate_left() self._relative_direction -= 90 if self._relative_direction == 90: self._rotation_motion = -1 elif self._relative_direction == -90: self._rotation_motion = 1
class BuffedFromElixirOfPower(StatModifyingBuffEffect): def __init__(self): super().__init__(BUFF_TYPE, {HeroStat.DAMAGE: DAMAGE_MODIFIER_INCREASE}) self.timer = PeriodicTimer(Millis(300)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): game_state.game_world.visual_effects.append( VisualRect((0, 0, 0), game_state.game_world.player_entity.get_center_position(), 6, 18, Millis(200), 3))
class IncreasedSpeedAfterDash(StatModifyingBuffEffect): def __init__(self): super().__init__(BUFF_SPEED, {HeroStat.MOVEMENT_SPEED: 0.4}) self.timer = PeriodicTimer(Millis(100)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): game_state.visual_effects.append( VisualCircle((150, 200, 250), game_state.player_entity.get_center_position(), 5, 10, Millis(200), 0))
class ChannelingStomp(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(Millis(80)) self.graphics_size = 40 def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.add_one() game_state.game_world.player_entity.set_not_moving() def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis) -> bool: if self.timer.update_and_check_if_ready(time_passed): visual_effect = VisualCircle( (250, 250, 250), buffed_entity.get_center_position(), self.graphics_size, self.graphics_size + 10, Millis(70), 2, None) self.graphics_size -= 7 game_state.game_world.visual_effects.append(visual_effect) return False 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.game_world.player_entity.get_center_position( ) distance = 80 affected_enemies = game_state.game_world.get_enemies_within_x_y_distance_of( distance, hero_center_pos) game_state.game_world.visual_effects.append( VisualRect((50, 50, 50), hero_center_pos, distance * 2, int(distance * 2.1), Millis(200), 2, None)) game_state.game_world.visual_effects.append( VisualRect((150, 150, 0), hero_center_pos, distance, distance * 2, Millis(150), 3, None)) game_state.game_world.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 get_buff_type(self): return CHANNELING_STOMP
class ProtectedByStoneAmulet(StatModifyingBuffEffect): def __init__(self): super().__init__(BUFF_TYPE, {HeroStat.ARMOR: ARMOR_BONUS}) self.timer = PeriodicTimer(Millis(300)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): game_state.visual_effects.append( VisualCircle((130, 100, 60), buffed_entity.get_center_position(), 20, 40, Millis(100), 1, buffed_entity))
class NpcMind(AbstractNpcMind): def __init__(self, global_path_finder: GlobalPathFinder): super().__init__(global_path_finder) self.timer = PeriodicTimer(Millis(500)) def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): if random.random() < 0.8: npc.world_entity.set_not_moving() else: direction = random.choice(get_all_directions()) npc.world_entity.set_moving_in_dir(direction)
class RestoringHealthFromBrew(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(Millis(600)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): player_receive_healing(3, game_state) player_receive_mana(3, game_state) def get_buff_type(self): return BUFF_TYPE def buff_handle_event(self, event: Event) -> Optional[BuffEventOutcome]: if isinstance(event, PlayerLostHealthEvent): return BuffEventOutcome.cancel_effect()
class BuffEffect(AbstractBuffEffect): def __init__(self): self._timer = PeriodicTimer(DEBUFF_DAMAGE_INTERVAL) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self._timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, DAMAGE, DamageType.MAGIC) pos = buffed_entity.get_center_position() effect = VisualCircle((100, 150, 100), pos, 40, 50, Millis(500), 1) game_state.game_world.visual_effects += [effect] def get_buff_type(self): return BUFF_TYPE
class BuffEffect(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(DAMAGE_INTERVAL) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, DAMAGE, DamageType.PHYSICAL, damage_source=DAMAGE_SOURCE) def get_buff_type(self): return BUFF_TYPE
class BurntByFireball(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(FIREBALL_TALENT_BURN_INTERVAL) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): deal_player_damage_to_enemy(game_state, buffed_npc, 1, DamageType.MAGIC) game_state.visual_effects.append( VisualCircle((180, 50, 50), buffed_npc.world_entity.get_center_position(), 10, 20, Millis(50), 0, buffed_entity)) def get_buff_type(self): return BUFF_TYPE
class AfterStealthing(AbstractBuffEffect): def __init__(self): self.timer = PeriodicTimer(Millis(160)) def apply_middle_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): visual_effect = VisualCircle((250, 150, 250), buffed_entity.get_center_position(), 18, 25, Millis(220), 1, buffed_entity) game_state.visual_effects.append(visual_effect) def apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.modify_stat(HeroStat.DODGE_CHANCE, -DODGE_CHANCE_BONUS) def get_buff_type(self): return BUFF_POST_STEALTH
class ItemEffect(AbstractItemEffect): def __init__(self): self.timer = PeriodicTimer(Millis(5000)) def apply_middle_effect(self, game_state: GameState, time_passed: Millis): if self.timer.update_and_check_if_ready(time_passed): player_entity = game_state.player_entity player_center_position = player_entity.get_center_position() close_enemies = game_state.get_enemies_within_x_y_distance_of( 140, player_center_position) if close_enemies: damage_amount: float = MIN_DMG + random.random() * (MAX_DMG - MIN_DMG) deal_player_damage_to_enemy(game_state, close_enemies[0], damage_amount, DamageType.MAGIC) enemy_center_position = close_enemies[ 0].world_entity.get_center_position() game_state.visual_effects.append( VisualCircle((250, 250, 0), player_center_position, 50, 140, Millis(100), 1, player_entity)) game_state.visual_effects.append( VisualLine((250, 250, 0), player_center_position, enemy_center_position, Millis(80), 3))
def main(map_file_name: Optional[str]): map_file_path = MAP_DIR + (map_file_name or "map1.json") if Path(map_file_path).exists(): game_state = create_game_state_from_json_file(CAMERA_SIZE, map_file_path, HERO_ID) else: player_entity = create_hero_world_entity(HERO_ID, (0, 0)) player_state = create_player_state(HERO_ID) game_state = GameState(player_entity, [], [], [], [], [], CAMERA_SIZE, Rect(-250, -250, 500, 500), player_state, [], [], []) pygame.init() pygame_screen = pygame.display.set_mode(SCREEN_SIZE) images_by_sprite = load_images_by_sprite(ENTITY_SPRITE_INITIALIZERS) images_by_ui_sprite = load_images_by_ui_sprite(UI_ICON_SPRITE_PATHS, MAP_EDITOR_UI_ICON_SIZE) images_by_portrait_sprite = load_images_by_portrait_sprite( PORTRAIT_ICON_SPRITE_PATHS, PORTRAIT_ICON_SIZE) world_view = GameWorldView(pygame_screen, CAMERA_SIZE, SCREEN_SIZE, images_by_sprite) ui_view = MapEditorView(pygame_screen, CAMERA_SIZE, SCREEN_SIZE, images_by_sprite, images_by_ui_sprite, images_by_portrait_sprite) user_state = UserState.deleting_entities() is_mouse_button_down = False possible_grid_cell_sizes = [25, 50] grid_cell_size_index = 0 grid_cell_size = possible_grid_cell_sizes[grid_cell_size_index] camera_move_distance = 75 # must be a multiple of the grid size snapped_mouse_screen_position = (0, 0) snapped_mouse_world_position = (0, 0) exact_mouse_screen_position = (0, 0) is_snapped_mouse_within_world = True is_snapped_mouse_over_ui = False game_state.center_camera_on_player() game_state.camera_world_area.topleft = ( (game_state.camera_world_area.x // grid_cell_size) * grid_cell_size, (game_state.camera_world_area.y // grid_cell_size) * grid_cell_size) held_down_arrow_keys = set([]) clock = pygame.time.Clock() camera_pan_timer = PeriodicTimer(Millis(50)) shown_tab = EntityTab.ITEMS while True: for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() if event.type == pygame.MOUSEMOTION: exact_mouse_screen_position: Tuple[int, int] = event.pos ui_view.handle_mouse_movement(exact_mouse_screen_position) snapped_mouse_screen_position = ( (exact_mouse_screen_position[0] // grid_cell_size) * grid_cell_size, (exact_mouse_screen_position[1] // grid_cell_size) * grid_cell_size) snapped_mouse_world_position = sum_of_vectors( snapped_mouse_screen_position, game_state.camera_world_area.topleft) is_snapped_mouse_within_world = game_state.is_position_within_game_world( snapped_mouse_world_position) is_snapped_mouse_over_ui = ui_view.is_screen_position_within_ui( snapped_mouse_screen_position) if is_mouse_button_down and is_snapped_mouse_within_world and not is_snapped_mouse_over_ui: if user_state.placing_entity: if user_state.placing_entity.wall_type: _add_wall(game_state, snapped_mouse_world_position, user_state.placing_entity.wall_type) elif user_state.placing_entity.decoration_sprite: _add_decoration( user_state.placing_entity.decoration_sprite, game_state, snapped_mouse_world_position) elif user_state.deleting_entities: _delete_map_entities_from_position( game_state, snapped_mouse_world_position) else: _delete_map_decorations_from_position( game_state, snapped_mouse_world_position) if event.type == pygame.KEYDOWN: if event.key == pygame.K_s: save_file = map_file_path save_game_state_to_json_file(game_state, save_file) print("Saved state to " + save_file) elif event.key in [ pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT, pygame.K_UP ]: held_down_arrow_keys.add(event.key) elif event.key == pygame.K_q: user_state = UserState.deleting_entities() elif event.key == pygame.K_z: user_state = UserState.deleting_decorations() elif event.key == pygame.K_PLUS: grid_cell_size_index = (grid_cell_size_index + 1) % len(possible_grid_cell_sizes) grid_cell_size = possible_grid_cell_sizes[ grid_cell_size_index] elif event.key == pygame.K_v: shown_tab = EntityTab.ITEMS elif event.key == pygame.K_b: shown_tab = EntityTab.NPCS elif event.key == pygame.K_n: shown_tab = EntityTab.WALLS elif event.key == pygame.K_m: shown_tab = EntityTab.MISC if event.type == pygame.KEYUP: if event.key in held_down_arrow_keys: held_down_arrow_keys.remove(event.key) if event.type == pygame.MOUSEBUTTONDOWN: ui_view.handle_mouse_click() is_mouse_button_down = True if user_state.placing_entity: entity_being_placed = user_state.placing_entity if is_snapped_mouse_within_world and not is_snapped_mouse_over_ui: if entity_being_placed.is_player: game_state.player_entity.set_position( snapped_mouse_world_position) elif entity_being_placed.npc_type: _add_npc(entity_being_placed.npc_type, game_state, snapped_mouse_world_position) elif entity_being_placed.wall_type: _add_wall(game_state, snapped_mouse_world_position, entity_being_placed.wall_type) elif entity_being_placed.consumable_type: _add_consumable( entity_being_placed.consumable_type, game_state, snapped_mouse_world_position) elif entity_being_placed.item_type: _add_item(entity_being_placed.item_type, game_state, snapped_mouse_world_position) elif entity_being_placed.decoration_sprite: _add_decoration( entity_being_placed.decoration_sprite, game_state, snapped_mouse_world_position) elif entity_being_placed.money_amount: _add_money(entity_being_placed.money_amount, game_state, snapped_mouse_world_position) elif entity_being_placed.portal_id: _add_portal(entity_being_placed.portal_id, game_state, snapped_mouse_world_position) elif entity_being_placed.is_chest: _add_chest(game_state, snapped_mouse_world_position) else: raise Exception("Unknown entity: " + str(entity_being_placed)) elif user_state.deleting_entities: _delete_map_entities_from_position( game_state, snapped_mouse_world_position) else: _delete_map_decorations_from_position( game_state, snapped_mouse_world_position) if event.type == pygame.MOUSEBUTTONUP: is_mouse_button_down = False clock.tick() time_passed = clock.get_time() if camera_pan_timer.update_and_check_if_ready(time_passed): if pygame.K_RIGHT in held_down_arrow_keys: game_state.translate_camera_position((camera_move_distance, 0)) if pygame.K_LEFT in held_down_arrow_keys: game_state.translate_camera_position( (-camera_move_distance, 0)) if pygame.K_DOWN in held_down_arrow_keys: game_state.translate_camera_position((0, camera_move_distance)) if pygame.K_UP in held_down_arrow_keys: game_state.translate_camera_position( (0, -camera_move_distance)) entities_to_render = game_state.get_all_entities_to_render() decorations_to_render = game_state.get_decorations_to_render() world_view.render_world( all_entities_to_render=entities_to_render, decorations_to_render=decorations_to_render, player_entity=game_state.player_entity, is_player_invisible=game_state.player_state.is_invisible, player_active_buffs=game_state.player_state.active_buffs, camera_world_area=game_state.camera_world_area, non_player_characters=game_state.non_player_characters, visual_effects=game_state.visual_effects, render_hit_and_collision_boxes=ui_view. checkbox_show_entity_outlines.checked, player_health=game_state.player_state.health_resource.value, player_max_health=game_state.player_state.health_resource. max_value, entire_world_area=game_state.entire_world_area, entity_action_text=None) camera_world_area = game_state.camera_world_area world_area = game_state.entire_world_area camera_rect_ratio = ((camera_world_area.x - world_area.x) / world_area.w, (camera_world_area.y - world_area.y) / world_area.h, camera_world_area.w / world_area.w, camera_world_area.h / world_area.h) npc_positions_ratio = [ ((npc.world_entity.x - world_area.x) / world_area.w, (npc.world_entity.y - world_area.y) / world_area.h) for npc in game_state.non_player_characters ] wall_positions_ratio = [ ((wall.world_entity.x - world_area.x) / world_area.w, (wall.world_entity.y - world_area.y) / world_area.h) for wall in game_state.walls_state.walls ] if shown_tab == EntityTab.ITEMS: shown_entities = ITEM_ENTITIES elif shown_tab == EntityTab.NPCS: shown_entities = NPC_ENTITIES elif shown_tab == EntityTab.WALLS: shown_entities = WALL_ENTITIES elif shown_tab == EntityTab.MISC: shown_entities = MISC_ENTITIES else: raise Exception("Unknown entity tab: " + str(shown_tab)) ui_view.set_shown_tab(shown_tab) entity_icon_hovered_by_mouse = ui_view.render( entities=shown_entities, placing_entity=user_state.placing_entity, deleting_entities=user_state.deleting_entities, deleting_decorations=user_state.deleting_decorations, num_enemies=len(game_state.non_player_characters), num_walls=len(game_state.walls_state.walls), num_decorations=len( game_state.decorations_state.decoration_entities), grid_cell_size=grid_cell_size, mouse_screen_position=exact_mouse_screen_position, camera_rect_ratio=camera_rect_ratio, npc_positions_ratio=npc_positions_ratio, wall_positions_ratio=wall_positions_ratio) if is_mouse_button_down and entity_icon_hovered_by_mouse: user_state = UserState.placing_entity(entity_icon_hovered_by_mouse) if is_snapped_mouse_over_ui: pass # render nothing over UI elif not is_snapped_mouse_within_world: snapped_mouse_rect = Rect(snapped_mouse_screen_position[0], snapped_mouse_screen_position[1], grid_cell_size, grid_cell_size) ui_view.render_map_editor_mouse_rect((250, 50, 0), snapped_mouse_rect) elif user_state.placing_entity: entity_being_placed = user_state.placing_entity ui_view.render_map_editor_world_entity_at_position( entity_being_placed.sprite, entity_being_placed.entity_size, snapped_mouse_screen_position) elif user_state.deleting_entities: snapped_mouse_rect = Rect(snapped_mouse_screen_position[0], snapped_mouse_screen_position[1], grid_cell_size, grid_cell_size) ui_view.render_map_editor_mouse_rect((250, 250, 0), snapped_mouse_rect) elif user_state.deleting_decorations: snapped_mouse_rect = Rect(snapped_mouse_screen_position[0], snapped_mouse_screen_position[1], grid_cell_size, grid_cell_size) ui_view.render_map_editor_mouse_rect((0, 250, 250), snapped_mouse_rect) else: raise Exception("Unhandled user_state: " + str(user_state)) pygame.display.update()
class Charging(AbstractBuffEffect): def __init__(self): self.graphics_timer = PeriodicTimer(Millis(40)) self.time_since_start = 0 def apply_start_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.add_one() game_state.modify_hero_stat(HeroStat.MOVEMENT_SPEED, BONUS_SPEED_MULTIPLIER) game_state.player_entity.set_moving_in_dir(game_state.player_entity.direction) 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 apply_end_effect(self, game_state: GameState, buffed_entity: WorldEntity, buffed_npc: NonPlayerCharacter): game_state.player_state.stun_status.remove_one() game_state.modify_hero_stat(HeroStat.MOVEMENT_SPEED, -BONUS_SPEED_MULTIPLIER) def get_buff_type(self): return BUFF_TYPE_CHARGING
def __init__(self, map_file_name: Optional[str]): self.map_file_path = MAP_DIR + (map_file_name or "map1.json") possible_grid_cell_sizes = [GRID_CELL_SIZE, GRID_CELL_SIZE * 2] grid_cell_size_index = 0 self.grid_cell_size = possible_grid_cell_sizes[grid_cell_size_index] self.grid: Grid = None self.ui_view: MapEditorView = None self.game_state: GameState = None if Path(self.map_file_path).exists(): print("Loading map '%s' from file." % self.map_file_path) map_data = load_map_from_json_file(self.map_file_path) player_position = map_data.player_position self._set_game_world(map_data.game_world, player_position) self.config = map_data.map_editor_config if map_data.grid_string: print("Initializing grid from existing map data.") self.grid = Grid.deserialize(map_data.grid_string) else: if not self.config.disable_smart_grid: print( "Grid missing from existing map data. Generating one from world state ..." ) self.build_grid_from_game_state() else: print( "Grid disabled for this map. Using smart floor tiles will not work." ) else: print("Map file '%s' not found! New map is created." % self.map_file_path) player_position = (0, 0) game_world = GameWorldState( player_entity=None, consumables_on_ground=[], items_on_ground=[], money_piles_on_ground=[], non_player_characters=[], walls=[], entire_world_area=Rect(-250, -250, 1500, 1000), decoration_entities=[], portals=[], chests=[], shrines=[], dungeon_entrances=[], ) self._set_game_world(game_world, player_position) self.config = MapEditorConfig(disable_smart_grid=False) grid_size = (self.game_state.game_world.entire_world_area.w // GRID_CELL_SIZE, self.game_state.game_world.entire_world_area.h // GRID_CELL_SIZE) self.grid = Grid.create_from_rects(grid_size, []) pygame.init() pygame_screen = pygame.display.set_mode(SCREEN_SIZE) images_by_sprite = load_images_by_sprite(ENTITY_SPRITE_INITIALIZERS) images_by_ui_sprite = load_images_by_ui_sprite( UI_ICON_SPRITE_PATHS, MAP_EDITOR_UI_ICON_SIZE) images_by_portrait_sprite = load_images_by_portrait_sprite( PORTRAIT_ICON_SPRITE_PATHS, PORTRAIT_ICON_SIZE) world_view = GameWorldView(pygame_screen, CAMERA_SIZE, SCREEN_SIZE, images_by_sprite) self.render_outlines = False self.ui_view = MapEditorView( pygame_screen, self.game_state.camera_world_area, SCREEN_SIZE, images_by_sprite, images_by_ui_sprite, images_by_portrait_sprite, self.game_state.game_world.entire_world_area, self.game_state.game_world.player_entity.get_center_position(), ENTITIES_BY_TYPE, self.grid_cell_size, self.map_file_path) self._notify_ui_of_new_wall_positions() camera_move_distance = 75 # must be a multiple of the grid size held_down_arrow_keys = set([]) clock = pygame.time.Clock() camera_pan_timer = PeriodicTimer(Millis(50)) while True: # HANDLE USER INPUT for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() if event.type == pygame.MOUSEMOTION: action = self.ui_view.handle_mouse_movement(event.pos) if action: self._handle_action(action, self.grid_cell_size) if event.type == pygame.KEYDOWN: if event.key == pygame.K_s: self.save() elif event.key in [ pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT, pygame.K_UP ]: held_down_arrow_keys.add(event.key) elif event.key == pygame.K_PLUS: grid_cell_size_index = ( grid_cell_size_index + 1) % len(possible_grid_cell_sizes) grid_cell_size = possible_grid_cell_sizes[ grid_cell_size_index] self.ui_view.grid_cell_size = grid_cell_size else: self.ui_view.handle_key_down(event.key) if event.type == pygame.KEYUP: if event.key in held_down_arrow_keys: held_down_arrow_keys.remove(event.key) if event.type == pygame.MOUSEBUTTONDOWN: if event.button == PYGAME_MOUSE_LEFT_BUTTON: action = self.ui_view.handle_mouse_left_click() elif event.button == PYGAME_MOUSE_RIGHT_BUTTON: action = self.ui_view.handle_mouse_right_click() else: action = None if action: self._handle_action(action, self.grid_cell_size) elif event.type == pygame.MOUSEBUTTONUP: if event.button == PYGAME_MOUSE_LEFT_BUTTON: action = self.ui_view.handle_mouse_left_release() elif event.button == PYGAME_MOUSE_RIGHT_BUTTON: action = self.ui_view.handle_mouse_right_release() else: action = None if action: self._handle_action(action, self.grid_cell_size) # HANDLE TIME clock.tick() time_passed = clock.get_time() if camera_pan_timer.update_and_check_if_ready(time_passed): if pygame.K_RIGHT in held_down_arrow_keys: self.game_state.translate_camera_position( (camera_move_distance, 0)) if pygame.K_LEFT in held_down_arrow_keys: self.game_state.translate_camera_position( (-camera_move_distance, 0)) if pygame.K_DOWN in held_down_arrow_keys: self.game_state.translate_camera_position( (0, camera_move_distance)) if pygame.K_UP in held_down_arrow_keys: self.game_state.translate_camera_position( (0, -camera_move_distance)) self.ui_view.camera_world_area = self.game_state.camera_world_area self.ui_view.world_area = self.game_state.game_world.entire_world_area # RENDER world_view.render_world( all_entities_to_render=self.game_state. get_all_entities_to_render(), decorations_to_render=self.game_state. get_decorations_to_render(), player_entity=self.game_state.game_world.player_entity, is_player_invisible=self.game_state.player_state.is_invisible, player_active_buffs=self.game_state.player_state.active_buffs, camera_world_area=self.game_state.camera_world_area, non_player_characters=self.game_state.game_world. non_player_characters, visual_effects=self.game_state.game_world.visual_effects, render_hit_and_collision_boxes=self.render_outlines, player_health=self.game_state.player_state.health_resource. value, player_max_health=self.game_state.player_state.health_resource. max_value, entire_world_area=self.game_state.game_world.entire_world_area, entity_action_text=None) npc_positions = [ npc.world_entity.get_position() for npc in self.game_state.game_world.non_player_characters ] named_portal_positions = { p.world_entity.get_position(): p.portal_id.name for p in self.game_state.game_world.portals } named_npc_positions = { npc.world_entity.get_position(): npc.npc_type.name for npc in self.game_state.game_world.non_player_characters } named_world_positions = { **named_portal_positions, **named_npc_positions } fps_string = str(int(clock.get_fps())) self.ui_view.render( num_enemies=len( self.game_state.game_world.non_player_characters), num_walls=len(self.game_state.game_world.walls_state.walls), num_decorations=len(self.game_state.game_world. decorations_state.decoration_entities), npc_positions=npc_positions, player_position=self.game_state.game_world.player_entity. get_center_position(), grid=self.grid, named_world_positions=named_world_positions, fps_string=fps_string) pygame.display.flip()