Files
wicked-quest/wicked_quest.py

238 lines
9.3 KiB
Python

#!/usr/bin/env python3
import json
import os
from libstormgames import *
from src.level import Level
from src.object import Object
from src.player import Player
class WickedQuest:
def __init__(self):
"""Initialize game and load sounds."""
self.sounds = initialize_gui("Wicked Quest")
self.currentLevel = None
self.gameStartTime = None
self.lastThrowTime = 0
self.throwDelay = 250
self.player = None # Will be initialized when first level loads
def load_level(self, levelNumber):
"""Load a level from its JSON file."""
levelFile = f"levels/{levelNumber}.json"
try:
with open(levelFile, 'r') as f:
levelData = json.load(f)
# Create player if this is the first level
if self.player is None:
self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"], self.sounds)
else:
# Just update player position for new level
self.player.xPos = levelData["player_start"]["x"]
self.player.yPos = levelData["player_start"]["y"]
# Pass existing player to new level
self.currentLevel = Level(levelData, self.sounds, self.player)
# Announce level details
levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. {levelData['description']}"
messagebox(levelIntro)
return True
except FileNotFoundError:
speak("Level not found")
return False
def handle_input(self):
"""Process keyboard input for player actions."""
keys = pygame.key.get_pressed()
player = self.currentLevel.player
currentTime = pygame.time.get_ticks()
# Update running state
player.isRunning = keys[pygame.K_SPACE]
# Get current speed (handles both running and jumping)
currentSpeed = player.get_current_speed()
# Track movement distance for this frame
movementDistance = 0
# Horizontal movement
if keys[pygame.K_a]: # Left
movementDistance = currentSpeed
player.xPos -= currentSpeed
player.facingRight = False
elif keys[pygame.K_d]: # Right
movementDistance = currentSpeed
player.xPos += currentSpeed
player.facingRight = True
# Handle footsteps
if movementDistance > 0 and not player.isJumping:
player.distanceSinceLastStep += movementDistance
if player.should_play_footstep(currentTime):
play_sound(self.sounds[player.footstepSound])
player.distanceSinceLastStep = 0
player.lastStepTime = currentTime
# Status queries
if keys[pygame.K_c]:
speak(f"{player.get_coins()} gbone dust")
if keys[pygame.K_h]:
speak(f"{player.get_health()} HP")
if keys[pygame.K_l]:
speak(f"{player.get_lives()} lives")
if keys[pygame.K_j]: # Check jack o'lanterns
speak(f"{player.get_jack_o_lanterns()} jack o'lanterns")
if keys[pygame.K_f]:
currentTime = pygame.time.get_ticks()
if currentTime - self.lastThrowTime >= self.throwDelay:
self.currentLevel.throw_projectile()
self.lastThrowTime = currentTime
# Handle attack with either CTRL key
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
play_sound(self.sounds[player.currentWeapon.attackSound])
# Handle jumping
if keys[pygame.K_w] and not player.isJumping:
player.isJumping = True
player.jumpStartTime = currentTime
play_sound(self.sounds['jump'])
# Check if jump should end
if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration:
player.isJumping = False
play_sound(self.sounds[player.footstepSound]) # Landing sound
# Reset step distance tracking after landing
player.distanceSinceLastStep = 0
player.lastStepTime = currentTime
def display_level_stats(self, timeTaken):
"""Display level completion statistics."""
# Convert time from milliseconds to minutes:seconds
minutes = timeTaken // 60000
seconds = (timeTaken % 60000) // 1000
# Update time in stats
self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True)
report = [f"Level {self.currentLevel.levelId} Complete!"]
report.append(f"Time taken: {minutes} minutes and {seconds} seconds")
# Add all level stats
for key in self.currentLevel.player.stats.level:
if key != 'Total time': # Skip time since we already displayed it
report.append(f"{key}: {self.currentLevel.player.stats.get_level_stat(key)}")
pygame.mixer.stop()
display_text(report)
self.currentLevel.player.stats.reset_level()
def display_game_over(self, timeTaken):
"""Display game over screen with statistics."""
minutes = timeTaken // 60000
seconds = (timeTaken % 60000) // 1000
report = ["Game Over!"]
report.append(f"Time taken: {minutes} minutes and {seconds} seconds")
# Add all total stats
for key in self.currentLevel.player.stats.total:
if key not in ['Total time', 'levelsCompleted']: # Skip these
report.append(f"Total {key}: {self.currentLevel.player.stats.get_total_stat(key)}")
display_text(report)
def game_loop(self):
"""Main game loop handling updates and state changes."""
clock = pygame.time.Clock()
levelStartTime = pygame.time.get_ticks()
currentLevelNum = 1
while True:
currentTime = pygame.time.get_ticks()
# Game volume controls
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return
if event.key == pygame.K_PAGEUP:
adjust_master_volume(0.1)
elif event.key == pygame.K_PAGEDOWN:
adjust_master_volume(-0.1)
elif event.key == pygame.K_HOME:
adjust_bgm_volume(0.1)
elif event.key == pygame.K_END:
adjust_bgm_volume(-0.1)
elif event.key == pygame.K_INSERT:
adjust_sfx_volume(0.1)
elif event.key == pygame.K_DELETE:
adjust_sfx_volume(-0.1)
# Update game state
self.currentLevel.player.update(currentTime)
self.handle_input()
self.currentLevel.update_audio()
# Handle combat and projectiles
self.currentLevel.handle_combat(currentTime)
self.currentLevel.handle_projectiles(currentTime)
# Check for death first
if self.currentLevel.player.get_health() <= 0:
if self.currentLevel.player.get_lives() <= 0:
# Game over - use gameStartTime for total time
pygame.mixer.stop()
totalTime = pygame.time.get_ticks() - self.gameStartTime
self.display_game_over(totalTime)
return
else:
pygame.mixer.stop()
self.currentLevel.player._health = self.currentLevel.player._maxHealth
self.load_level(currentLevelNum)
levelStartTime = pygame.time.get_ticks() # Reset level timer
continue
# Handle collisions and check level completion
if self.currentLevel.handle_collisions():
# Level time is from levelStartTime
levelTime = pygame.time.get_ticks() - levelStartTime
self.display_level_stats(levelTime)
currentLevelNum += 1
if self.load_level(currentLevelNum):
levelStartTime = pygame.time.get_ticks() # Reset level timer for new level
continue
else:
# Game complete - use gameStartTime for total
totalTime = pygame.time.get_ticks() - self.gameStartTime
messagebox("Congratulations! You've completed all available levels!")
self.display_game_over(totalTime)
return
clock.tick(60) # 60 FPS
def run(self):
"""Main game loop with menu system."""
while True:
choice = game_menu(self.sounds, "play", "instructions", "learn_sounds", "credits", "donate", "exit")
if choice == "exit":
exit_game()
elif choice == "play":
self.player = None # Reset player for new game
self.gameStartTime = pygame.time.get_ticks() # Set game start time here
if self.load_level(1):
self.game_loop()
elif choice == "learn_sounds":
choice = learn_sounds(self.sounds)
if __name__ == "__main__":
game = WickedQuest()
game.run()