/
Vibek
/
First_Game
Обзор
Документация
Войти
/
Vibek
/
First_Game
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
models/player.py
319 строк
14 KB
Vibek
все перменные перенесены в конфиг, отрисовка динамическая
21 июн 2026, 20:32
21 июн 2026, 20:32
f32a834
Код
Авторство
О чём код?
import pygame import math from typing import Optional from pygame.math import Vector2 from models.entity import Entity from core.events import EventBus from core.config import Config from models.combat_states import CombatState from models.upgrade_catalog import UpgradeDefinition, EffectTarget from models.abilities.dash_ability import DashAbility from models.abilities.throw_ability import ThrowAbility from models.hitbox import HitboxDef class Player(Entity): """ Специализированная модель Игрока. Наследует базовую физику от Entity, добавляет: - Ограничение границ арены (Clamping) - Игровые статы (hp, stamina, force) """ def __init__(self, x: float, y: float, speed: float, size: tuple[int, int], bounds: pygame.Rect, bus: EventBus, hitbox: HitboxDef | None = None, art_angle_offset: float = 0.0) -> None: super().__init__( x, y, speed, size, bus, hp=1, hitbox=hitbox, art_angle_offset=art_angle_offset, ) self.state = CombatState.IDLE self.facing_direction = Vector2(1, 0) self.bounds = bounds self.max_hp = 1 self.hp = self.max_hp self.is_alive = True self.dash_ability = DashAbility() self.throw_ability = ThrowAbility() self.is_dashing = False self.dash_timer = 0.0 self.dash_direction = Vector2(1, 0) self.dash_speed = Config.Gameplay.DASH_SPEED self.is_invulnerable = False self.invulnerability_timer = 0.0 self.last_movement_dir = Vector2(1, 0) self.is_throwing = False self.max_stamina = Config.STAMINA_MAX self.stamina = float(self.max_stamina) self._last_emitted_stamina = int(self.stamina) self.max_force = Config.FORCE_MAX self.force = float(self.max_force) self._last_emitted_force = int(self.force) self.is_blocking = False self.parry_window_active = False self.parry_timer = 0.0 self.stun_timer = 0.0 self.stamina_regen_rate = float(Config.STAMINA_REGEN_RATE) self.has_dash_ability = False def update(self, dt: float, direction: Vector2, aim_vector: Vector2) -> None: """ Обновляет состояние игрока за один кадр. :param dt: Delta time (сек) :param direction: Нормализованный вектор направления от InputHandler :param aim_vector Вектор от игрока до мыши (направление взгляда) :param bounds: Прямоугольник игровой арены для ограничения движения """ if self.state == CombatState.DEAD: return self.facing_direction = aim_vector self.angle = math.degrees(math.atan2(aim_vector.y, aim_vector.x)) if direction.length_squared() > 0: self.last_movement_dir = direction.normalize() # 2. Обработка таймеров неуязвимости и рывка if self.is_invulnerable: self.invulnerability_timer -= dt if self.invulnerability_timer <= 0.0: self.is_invulnerable = False if self.is_dashing: self.dash_timer -= dt # Движение во время рывка (игнорирует обычный ввод) self.pos += self.dash_direction * self.dash_speed * dt if self.dash_timer <= 0.0: self.is_dashing = False elif self.state != CombatState.STUNNED: # Обычное движение, если не в рывке и не в стане super().update(dt, direction) self._update_timers_and_resources(dt) self.dash_ability.update(dt) self.throw_ability.update(dt) self._clamp_position() self._sync_rect() # Проверяем состояние здоровья if self.hp <= 0 and self.state != CombatState.DEAD: self.state = CombatState.DEAD self.is_alive = False self.bus.emit(Config.EVT_ENTITY_DIED, {"target_id": id(self), "type": "player"}) def _clamp_position(self) -> None: """Ограничивает хитбокс игрока внутри арены.""" self.clamp_to_bounds(self.bounds) def _update_timers_and_resources(self, dt: float) -> None: '''Менеджер ресурсов игрока.''' # 1. Обработка стана if self.state == CombatState.STUNNED: self.stun_timer -= dt if self.stun_timer <= 0.0: self.state = CombatState.IDLE self.stun_timer = 0.0 return # 2. Регенерация стамина if not self.is_blocking and self.stamina < self.max_stamina: old_int = int(self.stamina) self.stamina = min(self.max_stamina, self.stamina + self.stamina_regen_rate * dt) if int(self.stamina) != self._last_emitted_stamina: self._last_emitted_stamina = int(self.stamina) self.bus.emit(Config.EVT_STAMINA_CHANGED, {"value": self._last_emitted_stamina}) # 3. Окно парирования (тик вперёд) if self.parry_window_active: self.parry_timer -= dt if self.parry_timer <= 0.0: self.parry_window_active = False self.parry_timer = 0.0 def set_blocking(self, is_active: bool) -> None: '''Блок игрока.''' if self.is_throwing or self.state in (CombatState.STUNNED, CombatState.DEAD): return # Запускаем таймер ТОЛЬКО в момент нажатия (переход из False в True) if is_active and not self.is_blocking: self.parry_window_active = True self.parry_timer = Config.PARRY_WINDOW # Выключаем всё, если кнопку отпустили if not is_active: self.is_blocking = False self.parry_window_active = False self.parry_timer = 0.0 return self.is_blocking = is_active def is_in_parry_window(self) -> bool: """Проверка активного окна парирования. Вызывается CombatSystem.""" return self.is_blocking and self.parry_window_active def drain_stamina(self, amount: float) -> bool: """ Списывает стамину при контакте с атакой. Возвращает True, если защита пробита (переход в STUNNED). """ if self.state in (CombatState.DEAD, CombatState.STUNNED): return False self.stamina = max(0.0, self.stamina - amount) if int(self.stamina) != self._last_emitted_stamina: self._last_emitted_stamina = int(self.stamina) self.bus.emit(Config.EVT_STAMINA_CHANGED, {"value": self._last_emitted_stamina}) if self.stamina <= 0.0: # Пробит блок → мгновенный стан, сброс защитных флагов self.is_blocking = False self.parry_window_active = False self.parry_timer = 0.0 self.state = CombatState.STUNNED self.stun_timer = Config.STUN_DURATION self.bus.emit(Config.EVT_BLOCK_BROKEN, {}) return True return False def add_force(self, amount: float) -> None: """Получение Force. Вызывается из CombatSystem/WaveManager.""" if self.state == CombatState.DEAD: return self.force = min(self.max_force, self.force + amount) if int(self.force) != self._last_emitted_force: self._last_emitted_force = int(self.force) self.bus.emit(Config.EVT_FORCE_CHANGED, {"value": self._last_emitted_force}) def reset_resources(self, wave_transition: bool = False) -> None: """Сброс ресурсов между волнами.""" self.stamina = float(self.max_stamina) self._last_emitted_stamina = int(self.stamina) # ВАЖНО: Передаём и текущее, и максимальное значение для HUD self.bus.emit(Config.EVT_STAMINA_CHANGED, { "value": self._last_emitted_stamina, "max_value": int(self.max_stamina) }) if wave_transition: self.add_force(self.max_force * Config.FORCE_RESTORE_BETWEEN_WAVES) else: self.force = float(self.max_force) self._last_emitted_force = int(self.force) self.bus.emit(Config.EVT_FORCE_CHANGED, {"value": self._last_emitted_force}) def on_death(self) -> None: """Переопределяет хук Entity. Игрок-specific логика.""" self.state = CombatState.DEAD self.stamina = 0.0 self._last_emitted_stamina = 0 self.bus.emit(Config.EVT_STAMINA_CHANGED, {"value": 0}) self.bus.emit(Config.EVT_ENTITY_DIED, {"target_id": id(self), "type": "player"}) def apply_upgrade(self, upgrade: UpgradeDefinition) -> None: """ Применяет эффект улучшения к игроку. Инкапсулирует всю логику изменения статов внутри модели. """ if self.state == CombatState.DEAD: return if upgrade.effect_target == EffectTarget.STAT_MAX_STAMINA: self.max_stamina += upgrade.effect_value # Бонус: немного лечим игрока на величину улучшения, чтобы он чувствовал выгоду сразу self.stamina = min(self.max_stamina, self.stamina + upgrade.effect_value) self._last_emitted_stamina = int(self.stamina) self.bus.emit(Config.EVT_STAMINA_CHANGED, {"value": self._last_emitted_stamina, "max_value": int(self.max_stamina) }) elif upgrade.effect_target == EffectTarget.STAT_MOVE_SPEED: self.speed *= upgrade.effect_value elif upgrade.effect_target == EffectTarget.STAT_STAMINA_REGEN: self.stamina_regen_rate += upgrade.effect_value elif upgrade.effect_target == EffectTarget.ABILITY_UNLOCK: if upgrade.id == "ACTIVE_DASH": if self.dash_ability.is_unlocked: return from models.abilities.dash_ability import DashAbility self.dash_ability = DashAbility(cost=upgrade.usage_cost, cooldown=upgrade.cooldown) self.dash_ability.is_unlocked = True print(f"[Upgrade] Ability UNLOCKED: {upgrade.name}") elif upgrade.id == "ACTIVE_THROW": if self.throw_ability.is_unlocked: return self.throw_ability = ThrowAbility(cost=upgrade.usage_cost, cooldown=upgrade.cooldown) self.throw_ability.is_unlocked = True print(f"[Upgrade] Ability UNLOCKED: {upgrade.name}") print(f"[Upgrade] Successfully applied: {upgrade.name}") def get_unavailable_upgrade_ids(self) -> frozenset[str]: """ID улучшений, которые больше нельзя предлагать игроку.""" excluded: set[str] = set() if self.dash_ability.is_unlocked: excluded.add("ACTIVE_DASH") if self.throw_ability.is_unlocked: excluded.add("ACTIVE_THROW") return frozenset(excluded) def activate_dash(self) -> bool: """ Пытается активировать рывок. Проверяет статы и устанавливает флаги. Физика рывка обрабатывается в методе update(). """ if self.state in (CombatState.DEAD, CombatState.STUNNED) or self.is_dashing: return False # Если игрок стоит на месте, рываем в сторону взгляда dash_dir = self.last_movement_dir if self.last_movement_dir.length_squared() > 0 else self.facing_direction self.is_dashing = True self.dash_timer = Config.Gameplay.DASH_DURATION self.dash_direction = dash_dir.normalize() self.is_invulnerable = True self.invulnerability_timer = Config.Gameplay.INVULNERABILITY_DURATION return True def start_throw(self) -> bool: """ Разрешает начать бросок. В отличие от Dash, не меняет физику игрока, а только ставит флаг запрета действий. """ if self.state in (CombatState.DEAD, CombatState.STUNNED) or self.is_throwing: return False self.is_throwing = True return True def cancel_active_abilities(self) -> None: """Прерывает незавершённые активные действия между волнами или сценами.""" if self.state == CombatState.DEAD: return self.is_throwing = False self.is_dashing = False self.dash_timer = 0.0 self.is_invulnerable = False self.invulnerability_timer = 0.0 self.is_blocking = False self.parry_window_active = False self.parry_timer = 0.0