Compare commits
1 Commits
7eb353677a
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c442261d9 |
36
src/level.py
36
src/level.py
@@ -10,7 +10,6 @@ from src.grasping_hands import GraspingHands
|
|||||||
from src.grave import GraveObject
|
from src.grave import GraveObject
|
||||||
from src.object import Object
|
from src.object import Object
|
||||||
from src.player import Player
|
from src.player import Player
|
||||||
from src.projectile import Projectile
|
|
||||||
from src.powerup import PowerUp
|
from src.powerup import PowerUp
|
||||||
from src.skull_storm import SkullStorm
|
from src.skull_storm import SkullStorm
|
||||||
|
|
||||||
@@ -200,8 +199,8 @@ class Level:
|
|||||||
self.objects.append(gameObject)
|
self.objects.append(gameObject)
|
||||||
enemyCount = len(self.enemies)
|
enemyCount = len(self.enemies)
|
||||||
coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken"))
|
coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken"))
|
||||||
player.stats.update_stat("Enemies remaining", enemyCount)
|
player.stats.set_stat("Enemies remaining", enemyCount, level_only=True)
|
||||||
player.stats.update_stat("Coffins remaining", coffinCount)
|
player.stats.set_stat("Coffins remaining", coffinCount, level_only=True)
|
||||||
|
|
||||||
def update_audio(self):
|
def update_audio(self):
|
||||||
"""Update all audio and entity state."""
|
"""Update all audio and entity state."""
|
||||||
@@ -310,16 +309,22 @@ class Level:
|
|||||||
def handle_combat(self, currentTime):
|
def handle_combat(self, currentTime):
|
||||||
"""Handle combat interactions between player and enemies"""
|
"""Handle combat interactions between player and enemies"""
|
||||||
# Only get attack range if attack is active
|
# Only get attack range if attack is active
|
||||||
if self.player.currentWeapon and self.player.currentWeapon.is_attack_active(currentTime):
|
if self.player.currentWeapon and self.player.currentWeapon.is_attack_active():
|
||||||
attackRange = self.player.currentWeapon.get_attack_range(self.player.xPos, self.player.facingRight)
|
# Calculate attack range manually
|
||||||
|
if self.player.facingRight:
|
||||||
|
attackRange = (self.player.xPos, self.player.xPos + self.player.currentWeapon.range_value)
|
||||||
|
else:
|
||||||
|
attackRange = (self.player.xPos - self.player.currentWeapon.range_value, self.player.xPos)
|
||||||
|
|
||||||
# Check for enemy hits
|
# Check for enemy hits
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]:
|
if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]:
|
||||||
# Only damage and play sound if this is a new hit for this attack
|
# Use libstormgames weapon hit detection
|
||||||
if self.player.currentWeapon.register_hit(enemy, currentTime):
|
if self.player.currentWeapon.can_hit_target(enemy.xPos, self.player.xPos, self.player.facingRight, id(enemy)):
|
||||||
play_sound(self.sounds[self.player.currentWeapon.hitSound])
|
damage = self.player.currentWeapon.hit_target(id(enemy))
|
||||||
enemy.take_damage(self.player.currentWeapon.damage)
|
if damage > 0:
|
||||||
|
play_sound(self.sounds[self.player.currentWeapon.hit_sound])
|
||||||
|
enemy.take_damage(damage)
|
||||||
|
|
||||||
# Check for coffin hits
|
# Check for coffin hits
|
||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
@@ -516,13 +521,16 @@ class Level:
|
|||||||
|
|
||||||
# Check for enemy hits
|
# Check for enemy hits
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if enemy.isActive and abs(proj.x - enemy.xPos) < 1:
|
if enemy.isActive and proj.check_collision(enemy.xPos, 1.0):
|
||||||
proj.hit_enemy(enemy)
|
damage = proj.hit()
|
||||||
|
enemy.take_damage(damage)
|
||||||
self.projectiles.remove(proj)
|
self.projectiles.remove(proj)
|
||||||
# Calculate volume and pan for splat sound based on final position
|
# Calculate volume and pan for splat sound based on final position
|
||||||
volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x)
|
proj_pos = proj.get_position()
|
||||||
|
proj_x = proj_pos if isinstance(proj_pos, (int, float)) else proj_pos[0]
|
||||||
|
volume, left, right = calculate_volume_and_pan(self.player.xPos, proj_x)
|
||||||
if volume > 0: # Only play if within audible range
|
if volume > 0: # Only play if within audible range
|
||||||
obj_play(self.sounds, "pumpkin_splat", self.player.xPos, proj.x, loop=False)
|
obj_play(self.sounds, "pumpkin_splat", self.player.xPos, proj_x, loop=False)
|
||||||
break
|
break
|
||||||
|
|
||||||
def throw_projectile(self):
|
def throw_projectile(self):
|
||||||
@@ -532,6 +540,6 @@ class Level:
|
|||||||
speak("No jack o'lanterns to throw!")
|
speak("No jack o'lanterns to throw!")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"]))
|
self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"], speed=0.2, damage=5, max_range=12))
|
||||||
# Play throw sound
|
# Play throw sound
|
||||||
play_sound(self.sounds["throw_jack_o_lantern"])
|
play_sound(self.sounds["throw_jack_o_lantern"])
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
from libstormgames import *
|
from libstormgames import *
|
||||||
from src.stat_tracker import StatTracker
|
|
||||||
from src.weapon import Weapon
|
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
@@ -27,7 +25,7 @@ class Player:
|
|||||||
self._lives = 1
|
self._lives = 1
|
||||||
self.distanceSinceLastStep = 0
|
self.distanceSinceLastStep = 0
|
||||||
self.stepDistance = 0.5
|
self.stepDistance = 0.5
|
||||||
self.stats = StatTracker()
|
self.stats = StatTracker({"Bone dust": 0, "Enemies killed": 0, "Coffins broken": 0, "Items collected": 0, "Total time": 0, "levelsCompleted": 0})
|
||||||
self.sounds = sounds
|
self.sounds = sounds
|
||||||
|
|
||||||
# Footstep tracking
|
# Footstep tracking
|
||||||
@@ -67,10 +65,10 @@ class Player:
|
|||||||
Weapon(
|
Weapon(
|
||||||
name="rusty_shovel",
|
name="rusty_shovel",
|
||||||
damage=2,
|
damage=2,
|
||||||
range=2,
|
range_value=2,
|
||||||
attackSound="player_shovel_attack",
|
attack_sound="player_shovel_attack",
|
||||||
hitSound="player_shovel_hit",
|
hit_sound="player_shovel_hit",
|
||||||
attackDuration=200, # 200ms attack duration
|
attack_duration=200, # 200ms attack duration
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,7 +104,7 @@ class Player:
|
|||||||
if currentTime >= self.webPenaltyEndTime:
|
if currentTime >= self.webPenaltyEndTime:
|
||||||
self.moveSpeed *= 2 # Restore speed
|
self.moveSpeed *= 2 # Restore speed
|
||||||
if self.currentWeapon:
|
if self.currentWeapon:
|
||||||
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed
|
self.currentWeapon.attack_duration *= 0.5 # Restore attack speed
|
||||||
del self.webPenaltyEndTime
|
del self.webPenaltyEndTime
|
||||||
|
|
||||||
# Check invincibility status
|
# Check invincibility status
|
||||||
@@ -166,7 +164,7 @@ class Player:
|
|||||||
|
|
||||||
def get_step_distance(self):
|
def get_step_distance(self):
|
||||||
"""Get step distance based on current speed"""
|
"""Get step distance based on current speed"""
|
||||||
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0
|
weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
|
||||||
totalMultiplier = weaponBonus
|
totalMultiplier = weaponBonus
|
||||||
|
|
||||||
if self.isRunning or self.isJumping:
|
if self.isRunning or self.isJumping:
|
||||||
@@ -176,7 +174,7 @@ class Player:
|
|||||||
|
|
||||||
def get_step_interval(self):
|
def get_step_interval(self):
|
||||||
"""Get minimum time between steps based on current speed"""
|
"""Get minimum time between steps based on current speed"""
|
||||||
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0
|
weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
|
||||||
totalMultiplier = weaponBonus
|
totalMultiplier = weaponBonus
|
||||||
|
|
||||||
if self.isRunning or self.isJumping:
|
if self.isRunning or self.isJumping:
|
||||||
@@ -199,7 +197,7 @@ class Player:
|
|||||||
def get_current_speed(self):
|
def get_current_speed(self):
|
||||||
"""Calculate current speed based on state and weapon"""
|
"""Calculate current speed based on state and weapon"""
|
||||||
baseSpeed = self.moveSpeed
|
baseSpeed = self.moveSpeed
|
||||||
weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0
|
weaponBonus = self.currentWeapon.stat_bonuses.get("speed", 1.0) if self.currentWeapon else 1.0
|
||||||
|
|
||||||
if self.isJumping or self.isRunning:
|
if self.isJumping or self.isRunning:
|
||||||
return baseSpeed * self.runMultiplier * weaponBonus
|
return baseSpeed * self.runMultiplier * weaponBonus
|
||||||
@@ -207,7 +205,7 @@ class Player:
|
|||||||
|
|
||||||
def get_current_jump_duration(self):
|
def get_current_jump_duration(self):
|
||||||
"""Calculate current jump duration based on weapon bonus"""
|
"""Calculate current jump duration based on weapon bonus"""
|
||||||
weaponBonus = self.currentWeapon.jumpDurationBonus if self.currentWeapon else 1.0
|
weaponBonus = self.currentWeapon.stat_bonuses.get("jump_duration", 1.0) if self.currentWeapon else 1.0
|
||||||
return int(self.jumpDuration * weaponBonus)
|
return int(self.jumpDuration * weaponBonus)
|
||||||
|
|
||||||
def set_footstep_sound(self, soundName):
|
def set_footstep_sound(self, soundName):
|
||||||
@@ -304,7 +302,9 @@ class Player:
|
|||||||
|
|
||||||
def start_attack(self, currentTime):
|
def start_attack(self, currentTime):
|
||||||
"""Attempt to start an attack with the current weapon"""
|
"""Attempt to start an attack with the current weapon"""
|
||||||
if self.currentWeapon and self.currentWeapon.start_attack(currentTime):
|
if self.currentWeapon and self.currentWeapon.can_attack():
|
||||||
|
damage = self.currentWeapon.attack()
|
||||||
|
if damage > 0:
|
||||||
self.isAttacking = True
|
self.isAttacking = True
|
||||||
self.lastAttackTime = currentTime
|
self.lastAttackTime = currentTime
|
||||||
return True
|
return True
|
||||||
@@ -312,6 +312,11 @@ class Player:
|
|||||||
|
|
||||||
def get_attack_range(self, currentTime):
|
def get_attack_range(self, currentTime):
|
||||||
"""Get the current attack's range based on position and facing direction"""
|
"""Get the current attack's range based on position and facing direction"""
|
||||||
if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime):
|
if not self.currentWeapon or not self.currentWeapon.is_attack_active():
|
||||||
return None
|
return None
|
||||||
return self.currentWeapon.get_attack_range(self.xPos, self.facingRight)
|
|
||||||
|
# Calculate attack range based on position and facing direction
|
||||||
|
if self.facingRight:
|
||||||
|
return (self.xPos, self.xPos + self.currentWeapon.range_value)
|
||||||
|
else:
|
||||||
|
return (self.xPos - self.currentWeapon.range_value, self.xPos)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import pygame
|
import pygame
|
||||||
from libstormgames import *
|
from libstormgames import *
|
||||||
from src.object import Object
|
from src.object import Object
|
||||||
from src.weapon import Weapon
|
|
||||||
|
|
||||||
|
|
||||||
class PowerUp(Object):
|
class PowerUp(Object):
|
||||||
@@ -100,7 +99,16 @@ class PowerUp(Object):
|
|||||||
|
|
||||||
self.check_for_nunchucks(player)
|
self.check_for_nunchucks(player)
|
||||||
elif self.item_type == "witch_broom":
|
elif self.item_type == "witch_broom":
|
||||||
broomWeapon = Weapon.create_witch_broom()
|
broomWeapon = Weapon(
|
||||||
|
name="witch_broom",
|
||||||
|
damage=3,
|
||||||
|
range_value=3,
|
||||||
|
attack_sound="player_broom_attack",
|
||||||
|
hit_sound="player_broom_hit",
|
||||||
|
cooldown=500,
|
||||||
|
attack_duration=200,
|
||||||
|
stat_bonuses={"speed": 1.17, "jump_duration": 1.25}
|
||||||
|
)
|
||||||
player.add_weapon(broomWeapon)
|
player.add_weapon(broomWeapon)
|
||||||
player.equip_weapon(broomWeapon)
|
player.equip_weapon(broomWeapon)
|
||||||
elif self.item_type == "spiderweb":
|
elif self.item_type == "spiderweb":
|
||||||
@@ -112,7 +120,7 @@ class PowerUp(Object):
|
|||||||
# Half speed and double attack time for 15 seconds
|
# Half speed and double attack time for 15 seconds
|
||||||
player.moveSpeed *= 0.5
|
player.moveSpeed *= 0.5
|
||||||
if player.currentWeapon:
|
if player.currentWeapon:
|
||||||
player.currentWeapon.attackDuration *= 2
|
player.currentWeapon.attack_duration *= 2
|
||||||
# Set timer for penalty removal
|
# Set timer for penalty removal
|
||||||
player.webPenaltyEndTime = pygame.time.get_ticks() + 15000
|
player.webPenaltyEndTime = pygame.time.get_ticks() + 15000
|
||||||
|
|
||||||
@@ -135,11 +143,19 @@ class PowerUp(Object):
|
|||||||
and "guts" in player.collectedItems
|
and "guts" in player.collectedItems
|
||||||
and not any(weapon.name == "nunchucks" for weapon in player.weapons)
|
and not any(weapon.name == "nunchucks" for weapon in player.weapons)
|
||||||
):
|
):
|
||||||
nunchucksWeapon = Weapon.create_nunchucks()
|
nunchucksWeapon = Weapon(
|
||||||
|
name="nunchucks",
|
||||||
|
damage=6,
|
||||||
|
range_value=4,
|
||||||
|
attack_sound="player_nunchuck_attack",
|
||||||
|
hit_sound="player_nunchuck_hit",
|
||||||
|
cooldown=250,
|
||||||
|
attack_duration=100
|
||||||
|
)
|
||||||
player.add_weapon(nunchucksWeapon)
|
player.add_weapon(nunchucksWeapon)
|
||||||
player.equip_weapon(nunchucksWeapon)
|
player.equip_weapon(nunchucksWeapon)
|
||||||
basePoints = nunchucksWeapon.damage * 1000
|
basePoints = nunchucksWeapon.damage * 1000
|
||||||
rangeModifier = nunchucksWeapon.range * 500
|
rangeModifier = nunchucksWeapon.range_value * 500
|
||||||
player.scoreboard.increase_score(basePoints + rangeModifier)
|
player.scoreboard.increase_score(basePoints + rangeModifier)
|
||||||
play_sound(self.sounds["get_nunchucks"])
|
play_sound(self.sounds["get_nunchucks"])
|
||||||
player.stats.update_stat("Items collected", 1)
|
player.stats.update_stat("Items collected", 1)
|
||||||
|
|||||||
191
wicked_quest.py
191
wicked_quest.py
@@ -10,7 +10,6 @@ from src.level import Level
|
|||||||
from src.object import Object
|
from src.object import Object
|
||||||
from src.player import Player
|
from src.player import Player
|
||||||
from src.game_selection import select_game, get_level_path
|
from src.game_selection import select_game, get_level_path
|
||||||
from src.save_manager import SaveManager
|
|
||||||
from src.survival_generator import SurvivalGenerator
|
from src.survival_generator import SurvivalGenerator
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ class WickedQuest:
|
|||||||
self.player = None
|
self.player = None
|
||||||
self.currentGame = None
|
self.currentGame = None
|
||||||
self.runLock = False # Toggle behavior of the run keys
|
self.runLock = False # Toggle behavior of the run keys
|
||||||
self.saveManager = SaveManager()
|
self.saveManager = SaveManager("wicked-quest")
|
||||||
self.survivalGenerator = None
|
self.survivalGenerator = None
|
||||||
self.lastBroomLandingTime = 0 # Timestamp to prevent ducking after broom landing
|
self.lastBroomLandingTime = 0 # Timestamp to prevent ducking after broom landing
|
||||||
self.survivalWave = 1
|
self.survivalWave = 1
|
||||||
@@ -75,7 +74,7 @@ class WickedQuest:
|
|||||||
del self.player.webPenaltyEndTime # Remove the penalty timer
|
del self.player.webPenaltyEndTime # Remove the penalty timer
|
||||||
self.player.moveSpeed *= 2 # Restore normal speed
|
self.player.moveSpeed *= 2 # Restore normal speed
|
||||||
if self.player.currentWeapon:
|
if self.player.currentWeapon:
|
||||||
self.player.currentWeapon.attackDuration *= 0.5 # Restore normal attack speed
|
self.player.currentWeapon.attack_duration *= 0.5 # Restore normal attack speed
|
||||||
|
|
||||||
# Pass existing player to new level
|
# Pass existing player to new level
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
@@ -117,10 +116,14 @@ class WickedQuest:
|
|||||||
messagebox("No save files found.")
|
messagebox("No save files found.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create menu options
|
# Create menu options with save info
|
||||||
options = []
|
options = []
|
||||||
|
save_infos = []
|
||||||
for save_file in save_files:
|
for save_file in save_files:
|
||||||
options.append(save_file['display_name'])
|
save_info = self.saveManager.get_save_info(save_file)
|
||||||
|
save_infos.append(save_info)
|
||||||
|
display_name = save_info.get("metadata", {}).get("display_name", "Unknown Save")
|
||||||
|
options.append(display_name)
|
||||||
|
|
||||||
options.append("Cancel")
|
options.append("Cancel")
|
||||||
|
|
||||||
@@ -131,9 +134,9 @@ class WickedQuest:
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# Find the corresponding save file
|
# Find the corresponding save file
|
||||||
for save_file in save_files:
|
for i, option in enumerate(options[:-1]): # Exclude "Cancel"
|
||||||
if save_file['display_name'] == choice:
|
if option == choice:
|
||||||
return save_file
|
return save_infos[i]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def auto_save(self):
|
def auto_save(self):
|
||||||
@@ -145,16 +148,31 @@ class WickedQuest:
|
|||||||
if not self.player.can_save():
|
if not self.player.can_save():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Spend the bone dust
|
||||||
|
if not self.player.spend_save_bone_dust(200):
|
||||||
|
return False
|
||||||
|
|
||||||
# Automatically create save
|
# Automatically create save
|
||||||
try:
|
try:
|
||||||
success, message = self.saveManager.create_save(
|
# Create save data structure
|
||||||
self.player,
|
save_data = {
|
||||||
self.currentLevel.levelId,
|
"player_state": self._serialize_player_state(),
|
||||||
self.gameStartTime,
|
"game_state": {
|
||||||
self.currentGame
|
"currentLevel": self.currentLevel.levelId,
|
||||||
)
|
"currentGame": self.currentGame,
|
||||||
|
"gameStartTime": self.gameStartTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create metadata for display
|
||||||
|
metadata = {
|
||||||
|
"display_name": f"{self.currentGame} Level {self.currentLevel.levelId}",
|
||||||
|
"level": self.currentLevel.levelId,
|
||||||
|
"game": self.currentGame
|
||||||
|
}
|
||||||
|
|
||||||
|
save_path = self.saveManager.create_save(save_data, metadata)
|
||||||
|
|
||||||
if success:
|
|
||||||
try:
|
try:
|
||||||
if 'save' in self.get_sounds():
|
if 'save' in self.get_sounds():
|
||||||
play_sound(self.get_sounds()['save'])
|
play_sound(self.get_sounds()['save'])
|
||||||
@@ -163,14 +181,130 @@ class WickedQuest:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error playing save sound: {e}")
|
print(f"Error playing save sound: {e}")
|
||||||
pass # Continue if save sound fails to play
|
pass # Continue if save sound fails to play
|
||||||
else:
|
|
||||||
print(f"Save failed: {message}")
|
|
||||||
|
|
||||||
return success
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during save: {e}")
|
print(f"Error during save: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _serialize_player_state(self):
|
||||||
|
"""Serialize player state for saving"""
|
||||||
|
return {
|
||||||
|
"xPos": self.player.xPos,
|
||||||
|
"yPos": self.player.yPos,
|
||||||
|
"health": self.player._health,
|
||||||
|
"maxHealth": self.player._maxHealth,
|
||||||
|
"lives": self.player._lives,
|
||||||
|
"coins": self.player._coins,
|
||||||
|
"saveBoneDust": self.player._saveBoneDust,
|
||||||
|
"jackOLanternCount": self.player._jack_o_lantern_count,
|
||||||
|
"shinBoneCount": self.player.shinBoneCount,
|
||||||
|
"inventory": self.player.inventory,
|
||||||
|
"collectedItems": self.player.collectedItems,
|
||||||
|
"weapons": self._serialize_weapons(self.player.weapons),
|
||||||
|
"currentWeaponName": self.player.currentWeapon.name if self.player.currentWeapon else None,
|
||||||
|
"stats": self.player.stats.to_dict(),
|
||||||
|
"scoreboard": self._serialize_scoreboard(self.player.scoreboard),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _serialize_weapons(self, weapons):
|
||||||
|
"""Serialize weapons for saving"""
|
||||||
|
serialized = []
|
||||||
|
for weapon in weapons:
|
||||||
|
serialized.append({
|
||||||
|
"name": weapon.name,
|
||||||
|
"damage": weapon.damage,
|
||||||
|
"range": weapon.range,
|
||||||
|
"attackSound": weapon.attack_sound,
|
||||||
|
"hitSound": weapon.hit_sound,
|
||||||
|
"attackDuration": weapon.attack_duration,
|
||||||
|
"speedBonus": getattr(weapon, "speedBonus", 1.0),
|
||||||
|
"jumpDurationBonus": getattr(weapon, "jumpDurationBonus", 1.0),
|
||||||
|
})
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
def _serialize_scoreboard(self, scoreboard):
|
||||||
|
"""Serialize scoreboard for saving"""
|
||||||
|
return {
|
||||||
|
"currentScore": getattr(scoreboard, "currentScore", 0),
|
||||||
|
"highScores": getattr(scoreboard, "highScores", []),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _restore_player_state(self, player_state):
|
||||||
|
"""Restore player state from save data"""
|
||||||
|
# Restore basic attributes
|
||||||
|
self.player.xPos = player_state["xPos"]
|
||||||
|
self.player.yPos = player_state["yPos"]
|
||||||
|
self.player._health = player_state["health"]
|
||||||
|
self.player._maxHealth = player_state["maxHealth"]
|
||||||
|
self.player._lives = player_state["lives"]
|
||||||
|
self.player._coins = player_state["coins"]
|
||||||
|
self.player._saveBoneDust = player_state["saveBoneDust"]
|
||||||
|
self.player._jack_o_lantern_count = player_state["jackOLanternCount"]
|
||||||
|
self.player.shinBoneCount = player_state["shinBoneCount"]
|
||||||
|
self.player.inventory = player_state["inventory"]
|
||||||
|
self.player.collectedItems = player_state["collectedItems"]
|
||||||
|
|
||||||
|
# Restore weapons
|
||||||
|
self.player.weapons = self._deserialize_weapons(player_state["weapons"])
|
||||||
|
|
||||||
|
# Restore current weapon
|
||||||
|
current_weapon_name = player_state.get("currentWeaponName")
|
||||||
|
if current_weapon_name:
|
||||||
|
for weapon in self.player.weapons:
|
||||||
|
if weapon.name == current_weapon_name:
|
||||||
|
self.player.currentWeapon = weapon
|
||||||
|
break
|
||||||
|
|
||||||
|
# Restore stats
|
||||||
|
if "stats" in player_state:
|
||||||
|
self.player.stats = StatTracker.from_dict(player_state["stats"])
|
||||||
|
else:
|
||||||
|
self.player.stats = StatTracker()
|
||||||
|
|
||||||
|
# Restore scoreboard
|
||||||
|
if "scoreboard" in player_state:
|
||||||
|
self.player.scoreboard = self._deserialize_scoreboard(player_state["scoreboard"])
|
||||||
|
else:
|
||||||
|
self.player.scoreboard = Scoreboard()
|
||||||
|
|
||||||
|
def _deserialize_weapons(self, weapon_data):
|
||||||
|
"""Deserialize weapons from save data"""
|
||||||
|
from src.weapon import Weapon
|
||||||
|
|
||||||
|
weapons = []
|
||||||
|
for data in weapon_data:
|
||||||
|
# Handle backward compatibility for old saves
|
||||||
|
speedBonus = data.get("speedBonus", 1.0)
|
||||||
|
jumpDurationBonus = data.get("jumpDurationBonus", 1.0)
|
||||||
|
|
||||||
|
# For old saves, restore proper bonuses for specific weapons
|
||||||
|
if data["name"] == "witch_broom" and speedBonus == 1.0:
|
||||||
|
speedBonus = 1.17
|
||||||
|
jumpDurationBonus = 1.25
|
||||||
|
|
||||||
|
weapon = Weapon(
|
||||||
|
name=data["name"],
|
||||||
|
damage=data["damage"],
|
||||||
|
range=data["range"],
|
||||||
|
attackSound=data["attackSound"],
|
||||||
|
hitSound=data["hitSound"],
|
||||||
|
attackDuration=data["attackDuration"],
|
||||||
|
speedBonus=speedBonus,
|
||||||
|
jumpDurationBonus=jumpDurationBonus,
|
||||||
|
)
|
||||||
|
weapons.append(weapon)
|
||||||
|
return weapons
|
||||||
|
|
||||||
|
def _deserialize_scoreboard(self, scoreboard_data):
|
||||||
|
"""Deserialize scoreboard from save data"""
|
||||||
|
scoreboard = Scoreboard()
|
||||||
|
if "currentScore" in scoreboard_data:
|
||||||
|
scoreboard.currentScore = scoreboard_data["currentScore"]
|
||||||
|
if "highScores" in scoreboard_data:
|
||||||
|
scoreboard.highScores = scoreboard_data["highScores"]
|
||||||
|
return scoreboard
|
||||||
|
|
||||||
def handle_input(self):
|
def handle_input(self):
|
||||||
"""Process keyboard input for player actions."""
|
"""Process keyboard input for player actions."""
|
||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
@@ -257,7 +391,7 @@ class WickedQuest:
|
|||||||
|
|
||||||
# Handle attack with either CTRL key
|
# Handle attack with either CTRL key
|
||||||
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
|
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
|
||||||
play_sound(self.get_sounds()[player.currentWeapon.attackSound])
|
play_sound(self.get_sounds()[player.currentWeapon.attack_sound])
|
||||||
|
|
||||||
# Handle jumping
|
# Handle jumping
|
||||||
if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping:
|
if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping:
|
||||||
@@ -291,14 +425,14 @@ class WickedQuest:
|
|||||||
seconds = (timeTaken % 60000) // 1000
|
seconds = (timeTaken % 60000) // 1000
|
||||||
|
|
||||||
# Update time in stats
|
# Update time in stats
|
||||||
self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True)
|
self.currentLevel.player.stats.set_stat('Total time', timeTaken, level_only=True)
|
||||||
|
|
||||||
report = [f"Time taken: {minutes} minutes and {seconds} seconds"]
|
report = [f"Time taken: {minutes} minutes and {seconds} seconds"]
|
||||||
|
|
||||||
# Add all level stats
|
# Add all level stats
|
||||||
for key in self.currentLevel.player.stats.level:
|
for key in self.currentLevel.player.stats.level:
|
||||||
if key != 'Total time': # Skip time since we already displayed it
|
if key != 'Total time': # Skip time since we already displayed it
|
||||||
report.append(f"{key}: {self.currentLevel.player.stats.get_level_stat(key)}")
|
report.append(f"{key}: {self.currentLevel.player.stats.get_stat(key)}")
|
||||||
|
|
||||||
report.append(f"Score: {int(self.currentLevel.levelScore)}")
|
report.append(f"Score: {int(self.currentLevel.levelScore)}")
|
||||||
|
|
||||||
@@ -324,7 +458,7 @@ class WickedQuest:
|
|||||||
# Add all total stats
|
# Add all total stats
|
||||||
for key in self.currentLevel.player.stats.total:
|
for key in self.currentLevel.player.stats.total:
|
||||||
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
||||||
report.append(f"Total {key}: {self.currentLevel.player.stats.get_total_stat(key)}")
|
report.append(f"Total {key}: {self.currentLevel.player.stats.get_stat(key, from_total=True)}")
|
||||||
|
|
||||||
report.append(f"Final Score: {self.player.scoreboard.get_score()}")
|
report.append(f"Final Score: {self.player.scoreboard.get_score()}")
|
||||||
|
|
||||||
@@ -350,7 +484,7 @@ class WickedQuest:
|
|||||||
# Add all total stats
|
# Add all total stats
|
||||||
for key in self.currentLevel.player.stats.total:
|
for key in self.currentLevel.player.stats.total:
|
||||||
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
||||||
report.append(f"Total {key}: {self.currentLevel.player.stats.get_total_stat(key)}")
|
report.append(f"Total {key}: {self.currentLevel.player.stats.get_stat(key, from_total=True)}")
|
||||||
|
|
||||||
if self.currentLevel.player.scoreboard.check_high_score():
|
if self.currentLevel.player.scoreboard.check_high_score():
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
@@ -479,8 +613,9 @@ class WickedQuest:
|
|||||||
elif choice == "load_game":
|
elif choice == "load_game":
|
||||||
selected_save = self.load_game_menu()
|
selected_save = self.load_game_menu()
|
||||||
if selected_save:
|
if selected_save:
|
||||||
success, save_data = self.saveManager.load_save(selected_save['filepath'])
|
try:
|
||||||
if success:
|
save_data, metadata = self.saveManager.load_save(selected_save['filepath'])
|
||||||
|
|
||||||
# Load the saved game
|
# Load the saved game
|
||||||
self.currentGame = save_data['game_state']['currentGame']
|
self.currentGame = save_data['game_state']['currentGame']
|
||||||
self.gameStartTime = save_data['game_state']['gameStartTime']
|
self.gameStartTime = save_data['game_state']['gameStartTime']
|
||||||
@@ -491,12 +626,12 @@ class WickedQuest:
|
|||||||
# Load the level
|
# Load the level
|
||||||
if self.load_level(current_level):
|
if self.load_level(current_level):
|
||||||
# Restore player state
|
# Restore player state
|
||||||
self.saveManager.restore_player_state(self.player, save_data)
|
self._restore_player_state(save_data['player_state'])
|
||||||
self.game_loop(current_level)
|
self.game_loop(current_level)
|
||||||
else:
|
else:
|
||||||
messagebox("Failed to load saved level.")
|
messagebox("Failed to load saved level.")
|
||||||
else:
|
except Exception as e:
|
||||||
messagebox(f"Failed to load save: {save_data}")
|
messagebox(f"Failed to load save: {e}")
|
||||||
elif choice == "play":
|
elif choice == "play":
|
||||||
self.currentGame = select_game(self.get_sounds())
|
self.currentGame = select_game(self.get_sounds())
|
||||||
if self.currentGame is None:
|
if self.currentGame is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user