/
kiraru
/
DeadBlocks
Обзор
Документация
Войти
/
kiraru
/
DeadBlocks
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
models/enemy.py
237 строк
9 KB
kiraru
маленький фикс + коментарии
23 июн 2026, 09:51
23 июн 2026, 09:51
516b2d4
Код
Авторство
О чём код?
# enemy.py — модель врагов с конечным автоматом состояний from models.geometry import FloatRect import math from constants import ( MOB_MAX_HP, MOB_DAMAGE, MOB_SPEED, MOB_DETECTION_RANGE, MOB_ATTACK_RANGE, MOB_ATTACK_COOLDOWN, MOB_PATROL_SPEED, BOSS_MAX_HP, BOSS_DAMAGE, BOSS_SPEED, BOSS_ATTACK_RANGE, BOSS_ATTACK_COOLDOWN, GRAVITY, TERMINAL_VELOCITY, EnemyState, DamageType, ENEMY_BUFF_INTERVAL, ENEMY_BUFF_PERCENT, ) class Enemy: """базовый класс врага. Содержит только логику, без рендеринга.""" def __init__( self, x: float, y: float, max_hp: int, damage: int, speed: float, patrol_speed: float, detection_range: float, attack_range: float, attack_cooldown: float, width: int = 36, height: int = 52, ): self.rect = FloatRect(x, y, width, height) self.velocity_x: float = 0.0 self.velocity_y: float = 0.0 self.on_ground: bool = False self.facing_right: bool = True self.base_hp: int = max_hp self.max_hp: int = max_hp self.hp: int = max_hp self.base_damage: int = damage self.damage: int = damage self.damage_type: str = DamageType.PHYSICAL self.speed: float = speed self.patrol_speed: float = patrol_speed self.detection_range: float = detection_range self.attack_range: float = attack_range self.attack_cooldown: float = attack_cooldown self.attack_timer: float = 0.0 self.state: str = EnemyState.PATROL self.patrol_direction: int = 1 self.patrol_timer: float = 0.0 self.patrol_duration: float = 2.5 # секунд в одном направлении # текущий уровень баффа, применённый к этому врагу. self._applied_buff_count: int = 0 # свойства @property def is_alive(self) -> bool: return self.hp > 0 @property def can_attack(self) -> bool: return self.attack_timer <= 0 # обновление def update(self, dt: float, player_rect: FloatRect) -> None: if not self.is_alive: self.state = EnemyState.DEAD return self._update_attack_timer(dt) self._apply_gravity(dt) self._update_ai(dt, player_rect) def apply_global_buff(self, global_buff_count: int) -> None: """применить глобальный уровень баффа.""" if global_buff_count <= self._applied_buff_count: return self._applied_buff_count = global_buff_count multiplier = (1 + ENEMY_BUFF_PERCENT) ** global_buff_count self.max_hp = int(self.base_hp * multiplier) self.damage = int(self.base_damage * multiplier) # при баффе враг полностью восстанавливает HP до нового максимума. self.hp = self.max_hp def _update_attack_timer(self, dt: float) -> None: if self.attack_timer > 0: self.attack_timer -= dt def _apply_gravity(self, dt: float) -> None: if not self.on_ground: self.velocity_y = min(self.velocity_y + GRAVITY * dt, TERMINAL_VELOCITY) def _update_ai(self, dt: float, player_rect: FloatRect) -> None: distance = self._distance_to(player_rect) if distance <= self.attack_range: self._enter_attack_state(player_rect) elif distance <= self.detection_range: self._chase(player_rect) else: self._patrol(dt) def _distance_to(self, target_rect: FloatRect) -> float: dx = target_rect.centerx - self.rect.centerx dy = target_rect.centery - self.rect.centery return math.hypot(dx, dy) def _enter_attack_state(self, player_rect: FloatRect) -> None: self.state = EnemyState.ATTACK self.velocity_x = 0.0 self._face_target(player_rect) def _chase(self, player_rect: FloatRect) -> None: self.state = EnemyState.CHASE self._face_target(player_rect) self.velocity_x = self.speed * (1 if player_rect.centerx > self.rect.centerx else -1) def _patrol(self, dt: float) -> None: self.state = EnemyState.PATROL self.patrol_timer += dt if self.patrol_timer >= self.patrol_duration: self.patrol_timer = 0.0 self.patrol_direction *= -1 self.velocity_x = self.patrol_speed * self.patrol_direction self.facing_right = self.patrol_direction > 0 def _face_target(self, target_rect: FloatRect) -> None: self.facing_right = target_rect.centerx > self.rect.centerx # атака def try_attack(self, player_rect: FloatRect) -> bool: """попытаться атаковать игрока. Возвращает True если удар прошёл.""" if not self.can_attack: return False if self._distance_to(player_rect) <= self.attack_range: self.attack_timer = self.attack_cooldown return True return False def take_damage(self, amount: int, damage_type: str = DamageType.PHYSICAL) -> None: effective = self._apply_resistance(amount, damage_type) self.hp = max(0, self.hp - effective) if self.hp > 0: self.state = EnemyState.HURT def _apply_resistance(self, amount: int, damage_type: str) -> int: """базовый враг не имеет сопротивлений. Переопределяется у босса.""" return amount def to_dict(self) -> dict: return { "type": self.__class__.__name__, "x": self.rect.x, "y": self.rect.y, "hp": self.hp, "applied_buff_count": self._applied_buff_count, } class Mob(Enemy): """обычный моб — использует стандартные константы моба.""" def __init__(self, x: float, y: float, hp_mult: float = 1.0, dmg_mult: float = 1.0): super().__init__( x, y, max_hp=int(MOB_MAX_HP * hp_mult), damage=int(MOB_DAMAGE * dmg_mult), speed=MOB_SPEED, patrol_speed=MOB_PATROL_SPEED, detection_range=MOB_DETECTION_RANGE, attack_range=MOB_ATTACK_RANGE, attack_cooldown=MOB_ATTACK_COOLDOWN, ) self.base_hp = int(MOB_MAX_HP * hp_mult) self.base_damage = int(MOB_DAMAGE * dmg_mult) class Boss(Enemy): """босс — усиленный враг с сопротивлениями к типам урона.""" def __init__( self, x: float, y: float, hp_mult: float = 1.0, dmg_mult: float = 1.0, weak_damage_type: str = DamageType.PHYSICAL, ): super().__init__( x, y, max_hp=int(BOSS_MAX_HP * hp_mult), damage=int(BOSS_DAMAGE * dmg_mult), speed=BOSS_SPEED, patrol_speed=BOSS_SPEED * 0.5, detection_range=9999, # всегда замечает игрока attack_range=BOSS_ATTACK_RANGE, attack_cooldown=BOSS_ATTACK_COOLDOWN, width=56, height=72, ) self.base_hp = int(BOSS_MAX_HP * hp_mult) self.base_damage = int(BOSS_DAMAGE * dmg_mult) # тип урона, к которому босс уязвим (определяется при генерации) self.weak_damage_type: str = weak_damage_type # сопротивления: все типы имеют 50% сопротивление, кроме слабости self.resistances: dict[str, float] = { DamageType.PHYSICAL: 0.5, DamageType.FIRE: 0.5, DamageType.ICE: 0.5, DamageType.POISON: 0.5, DamageType.LIGHTNING: 0.5, } self.resistances[weak_damage_type] = 0.0 # нет сопротивления к слабости # фазы босса self.phase: int = 1 # 1 или 2 (при 50% хп входит в фазу 2) def update(self, dt: float, player_rect: FloatRect) -> None: super().update(dt, player_rect) self._update_phase() def _update_phase(self) -> None: if self.hp <= self.max_hp * 0.5 and self.phase == 1: self.phase = 2 self.speed *= 1.3 self.damage = int(self.damage * 1.2) def _apply_resistance(self, amount: int, damage_type: str) -> int: resist = self.resistances.get(damage_type, 0.5) return max(1, int(amount * (1.0 - resist))) def to_dict(self) -> dict: data = super().to_dict() data["weak_damage_type"] = self.weak_damage_type data["phase"] = self.phase return data