254 lines
8.6 KiB
Python
254 lines
8.6 KiB
Python
# -*- 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)
|