# -*- coding: utf-8 -*- import pygame from libstormgames import * from src.stat_tracker import StatTracker from src.weapon import Weapon class Player: def __init__(self, xPos, yPos, sounds): self.sounds = sounds # Movement attributes self.xPos = xPos self.yPos = yPos self.moveSpeed = 0.05 self.jumpDuration = 1000 # Jump duration in milliseconds self.jumpStartTime = 0 self.isDucking = False self.isJumping = False self.isRunning = False self.runMultiplier = 1.5 # Same multiplier as jumping self.facingRight = True # Stats and tracking self._health = 10 self._maxHealth = 10 self._lives = 1 self.distanceSinceLastStep = 0 self.stepDistance = 0.5 self.stats = StatTracker() self.sounds = sounds # Footstep tracking self.baseStepDistance = 0.8 self.baseStepInterval = 250 self.stepDistance = self.baseStepDistance self.minStepInterval = self.baseStepInterval self.distanceSinceLastStep = 0 self.lastStepTime = 0 self.isRunning = False self.runMultiplier = 1.5 # Inventory system self.inventory = [] self.collectedItems = [] self._coins = 0 self._jack_o_lantern_count = 0 self.shinBoneCount = 0 # Combat related attributes self.weapons = [] self.currentWeapon = None self.isAttacking = False self.lastAttackTime = 0 # Power-up states self.isInvincible = False self.invincibilityStartTime = 0 self.invincibilityDuration = 10000 # 10 seconds of invincibility # Initialize starting weapon (rusty shovel) self.add_weapon(Weapon( name="rusty_shovel", damage=2, range=2, attackSound="player_shovel_attack", hitSound="player_shovel_hit", attackDuration=200 # 200ms attack duration )) self.scoreboard = Scoreboard() def should_play_footstep(self, currentTime): """Check if it's time to play a footstep sound""" return (self.distanceSinceLastStep >= self.get_step_distance() and currentTime - self.lastStepTime >= self.get_step_interval()) def duck(self): """Start ducking""" if not self.isDucking and not self.isJumping: # Can't duck while jumping self.isDucking = True play_sound(self.sounds['duck']) return True return False def stand(self): """Stop ducking state and play sound""" if self.isDucking: self.isDucking = False play_sound(self.sounds['stand']) def update(self, currentTime): """Update player state""" if hasattr(self, 'webPenaltyEndTime'): if currentTime >= self.webPenaltyEndTime: self.moveSpeed *= 2 # Restore speed if self.currentWeapon: self.currentWeapon.attackDuration *= 0.5 # Restore attack speed del self.webPenaltyEndTime # Check invincibility status if self.isInvincible: remaining_time = (self.invincibilityStartTime + self.invincibilityDuration - currentTime) / 1000 # Convert to seconds # Handle countdown sounds if not hasattr(self, '_last_countdown'): self._last_countdown = 4 # Start counting from 4 to catch 3,2,1 current_second = int(remaining_time) if current_second < self._last_countdown and current_second <= 3 and current_second > 0: play_sound(self.sounds['end_of_invincibility_warning']) self._last_countdown = current_second # Check if invincibility has expired if currentTime - self.invincibilityStartTime >= self.invincibilityDuration: self.isInvincible = False speak("Invincibility wore off!") del self._last_countdown # Clean up countdown tracker def start_invincibility(self): """Activate invincibility from Hand of Glory""" self.isInvincible = True self.invincibilityStartTime = pygame.time.get_ticks() if hasattr(self, '_last_countdown'): del self._last_countdown # Reset countdown if it exists def extra_life(self): """Increment lives by 1""" self._lives += 1 def get_jack_o_lanterns(self): """Get number of jack o'lanterns""" return self._jack_o_lantern_count def add_jack_o_lantern(self): """Add a jack o'lantern""" self._jack_o_lantern_count += 1 def add_guts(self): """Apply guts, increase max_health by 2 if less than 20 else restore health""" if self._maxHealth < 20: self._maxHealth += 2 else: self._health = self._maxHealth def throw_projectile(self): """Throw a jack o'lantern if we have any""" if self.get_jack_o_lanterns() <= 0: return None self._jack_o_lantern_count -= 1 return { 'type': 'jack_o_lantern', 'start_x': self.xPos, 'direction': 1 if self.facingRight else -1 } def get_step_distance(self): """Get step distance based on current speed""" if self.isRunning or self.isJumping: return self.baseStepDistance / self.runMultiplier return self.baseStepDistance def get_step_interval(self): """Get minimum time between steps based on current speed""" if self.isRunning or self.isJumping: return self.baseStepInterval / self.runMultiplier return self.baseStepInterval def get_health(self): """Get current health""" return self._health def get_max_health(self): """Get current max health""" return self._maxHealth def restore_health(self): """Restore health to maximum""" self._health = self._maxHealth def get_current_speed(self): """Calculate current speed based on state""" baseSpeed = self.moveSpeed if self.isJumping or self.isRunning: return baseSpeed * self.runMultiplier return baseSpeed def set_footstep_sound(self, soundName): """Set the current footstep sound""" self.footstepSound = soundName def set_health(self, value): """Set health and handle death if needed.""" old_health = self._health # Oops, allow healing while invincible. if self.isInvincible and value < old_health: return self._health = max(0, value) # Health can't go below 0 if self._health == 0 and old_health > 0: self._lives -= 1 # Stop all current sounds before playing death sound pygame.mixer.stop() try: pygame.mixer.music.stop() except: pass cut_scene(self.sounds, 'lose_a_life') def set_max_health(self, value): """Set max health""" self._maxHealth = value def get_coins(self): """Get remaining coins""" return self._coins def get_lives(self): """Get remaining lives""" return self._lives def add_weapon(self, weapon): """Add a new weapon to inventory and equip if first weapon""" self.weapons.append(weapon) if len(self.weapons) == 1: # If this is our first weapon, equip it self.equip_weapon(weapon) def equip_weapon(self, weapon): """Equip a specific weapon""" if weapon in self.weapons: self.currentWeapon = weapon def add_item(self, item): """Add an item to inventory""" self.inventory.append(item) self.collectedItems.append(item) def start_attack(self, currentTime): """Attempt to start an attack with the current weapon""" if self.currentWeapon and self.currentWeapon.start_attack(currentTime): self.isAttacking = True self.lastAttackTime = currentTime return True return False def get_attack_range(self, currentTime): """Get the current attack's range based on position and facing direction""" if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): return None return self.currentWeapon.get_attack_range(self.xPos, self.facingRight)