New functionality added inspired by Wicked Quest game.

This commit is contained in:
Storm Dragon
2025-09-16 01:21:20 -04:00
parent a96f9744a9
commit 5e5d33256e
6 changed files with 1554 additions and 25 deletions

329
README.md
View File

@@ -14,6 +14,9 @@ A Python library to make creating audio games easier.
- Input handling and keyboard controls
- Menu systems and text display
- GUI initialization
- **NEW**: Statistics tracking with level/total separation
- **NEW**: Save/load management with atomic operations
- **NEW**: Combat systems with weapons and projectiles
## Installation
@@ -129,6 +132,9 @@ The library is organized into modules, each with a specific focus:
- **display**: Text display and GUI functionality
- **menu**: Menu systems
- **utils**: Utility functions and Game class
- **stat_tracker**: Statistics tracking system
- **save_manager**: Save/load management
- **combat**: Weapon and projectile systems
## Core Classes
@@ -297,6 +303,131 @@ Where `game-name` is the lowercase, hyphenated version of your game title. For e
"My Awesome Game" would use the directory `my-awesome-game`.
### StatTracker
Flexible statistics tracking with separate level and total counters:
```python
# Initialize with default stats
stats = sg.StatTracker({
"kills": 0,
"deaths": 0,
"score": 0,
"time_played": 0.0
})
# Update stats during gameplay
stats.update_stat("kills", 1) # Increment kills
stats.update_stat("time_played", 1.5) # Add playtime
# Access current values
level_kills = stats.get_stat("kills") # Current level kills
total_kills = stats.get_stat("kills", from_total=True) # All-time kills
# Reset level stats for new level (keeps totals)
stats.reset_level()
# Add new stats dynamically
stats.add_stat("boss_kills", 0)
# Serialization for save/load
stats_data = stats.to_dict()
restored_stats = sg.StatTracker.from_dict(stats_data)
```
### SaveManager
Atomic save/load operations with corruption detection:
```python
# Initialize save manager
save_manager = sg.SaveManager("my-rpg-game")
# Create a comprehensive save
game_state = {
"player": {
"level": 5,
"hp": 80,
"inventory": ["sword", "health_potion"]
},
"world": {
"current_area": "enchanted_forest",
"completed_quests": ["tutorial", "first_boss"]
},
"stats": stats.to_dict() # Include StatTracker data
}
# Add metadata for save selection screen
metadata = {
"display_name": "Enchanted Forest - Level 5",
"level": 5,
"playtime": "2.5 hours"
}
# Create the save (atomic operation)
save_path = save_manager.create_save(game_state, metadata)
# List and load saves
save_files = save_manager.get_save_files() # Newest first
if save_files:
loaded_data, loaded_metadata = save_manager.load_save(save_files[0])
restored_stats = sg.StatTracker.from_dict(loaded_data["stats"])
```
### Combat Systems
#### Weapons
```python
# Create weapons using factory methods
sword = sg.Weapon.create_sword("Iron Sword", damage=15)
dagger = sg.Weapon.create_dagger("Steel Dagger", damage=12)
# Custom weapon with stat bonuses
bow = sg.Weapon(
name="Elvish Bow", damage=18, range_value=8,
cooldown=600, stat_bonuses={"speed": 1.15}
)
# Combat usage
if sword.can_attack(): # Check cooldown
damage = sword.attack() # Start attack
if sword.can_hit_target(enemy_pos, player_pos, facing_right, "enemy1"):
actual_damage = sword.hit_target("enemy1")
sg.speak(f"Hit for {actual_damage} damage!")
```
#### Projectiles
```python
# Create projectiles
arrow = sg.Projectile.create_arrow(
start_pos=(player_x, player_y),
direction=(1, 0) # Moving right
)
# Game loop integration
active_projectiles = []
def update_projectiles():
for projectile in active_projectiles[:]:
if not projectile.update(): # Move and check range
active_projectiles.remove(projectile)
continue
# Check enemy collisions
for enemy in enemies:
if projectile.check_collision(enemy.position, enemy.size):
damage = projectile.hit()
enemy.take_damage(damage)
active_projectiles.remove(projectile)
break
```
## Key Functions
### Game Initialization and Control
@@ -644,40 +775,60 @@ def create_sound_environment(player_x, player_y):
```
### Complete Game Structure with Class-Based Architecture
### Complete Game Structure with New Systems
```python
import libstormgames as sg
import pygame
import random
class MyGame:
class ModernRPGGame:
def __init__(self):
# Create a Game instance that manages all subsystems
self.game = sg.Game("My Advanced Game").initialize()
self.game = sg.Game("Modern RPG Demo").initialize()
# Initialize new game systems
self.player_stats = sg.StatTracker({
"level": 1, "exp": 0, "hp": 100, "mp": 50,
"kills": 0, "deaths": 0, "playtime": 0.0,
"items_found": 0, "gold": 0
})
self.save_manager = sg.SaveManager("modern-rpg-demo")
# Combat system
self.player_weapon = sg.Weapon.create_sword("Starting Sword", damage=10)
self.projectiles = []
self.enemies = []
# Game state
self.player_x = 5
self.player_y = 5
self.current_area = "village"
self.difficulty = "normal"
# Load settings
try:
self.difficulty = self.game.config_service.local_config.get("settings", "difficulty")
self.difficulty = self.game.configService.localConfig.get("settings", "difficulty")
except:
self.game.config_service.local_config.add_section("settings")
self.game.config_service.local_config.set("settings", "difficulty", "normal")
self.game.config_service.write_local_config()
self.game.configService.localConfig.add_section("settings")
self.game.configService.localConfig.set("settings", "difficulty", "normal")
self.game.configService.write_local_config()
def play_game(self):
"""Main game loop demonstrating new systems."""
self.game.speak(f"Starting game on {self.difficulty} difficulty")
self.game.play_bgm("sounds/game_music.ogg")
start_time = pygame.time.get_ticks()
# Game loop
running = True
while running:
# Update game state
self.player_x += random.uniform(-0.2, 0.2)
current_time = pygame.time.get_ticks()
# Update playtime stats
playtime_hours = (current_time - start_time) / 3600000.0 # Convert to hours
self.player_stats.set_stat("playtime", playtime_hours, level_only=True)
# Handle input
for event in pygame.event.get():
@@ -685,26 +836,148 @@ class MyGame:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
self.game.scoreboard.increase_score()
self.game.speak(f"Score: {self.game.scoreboard.get_score()}")
# Combat example
self.player_attack()
elif event.key == pygame.K_s:
# Quick save
self.quick_save()
elif event.key == pygame.K_l:
# Quick load
self.quick_load()
# Add some random sounds
if random.random() < 0.05:
sounds = self.game.sound.get_sounds()
if "ambient" in sounds:
sg.play_random_positional(sounds, "ambient",
self.player_x, self.player_x + random.uniform(-10, 10))
# Update game systems
self.update_combat()
self.update_player_stats()
# Random events
if random.random() < 0.01:
self.random_encounter()
pygame.time.delay(50)
# Game over
position = self.game.scoreboard.check_high_score()
if position:
self.game.speak(f"New high score! Position {position}")
self.game.scoreboard.add_high_score()
# Game over - update total stats
total_playtime = self.player_stats.get_stat("playtime", from_total=True)
self.game.speak(f"Session ended. Total playtime: {total_playtime:.1f} hours")
return "menu"
def player_attack(self):
"""Handle player combat."""
if self.player_weapon.can_attack():
damage = self.player_weapon.attack()
self.game.speak("Attack!")
# Simulate hitting enemies
if self.enemies and self.player_weapon.is_attack_active():
enemy = self.enemies[0] # Attack first enemy
if self.player_weapon.can_hit_target(
enemy.position, (self.player_x, self.player_y),
facing_right=True, target_id=enemy.id
):
actual_damage = self.player_weapon.hit_target(enemy.id)
self.player_stats.update_stat("damage_dealt", actual_damage)
# Remove enemy if defeated
if enemy.take_damage(actual_damage):
self.enemies.remove(enemy)
self.player_stats.update_stat("kills", 1)
self.player_stats.update_stat("exp", 25)
self.game.speak("Enemy defeated!")
def update_combat(self):
"""Update combat systems."""
# Update projectiles
for projectile in self.projectiles[:]:
if not projectile.update():
self.projectiles.remove(projectile)
continue
# Check enemy collisions
for enemy in self.enemies[:]:
if projectile.check_collision(enemy.position, enemy.size):
damage = projectile.hit()
if enemy.take_damage(damage):
self.enemies.remove(enemy)
self.player_stats.update_stat("kills", 1)
self.projectiles.remove(projectile)
break
def update_player_stats(self):
"""Handle player progression."""
exp = self.player_stats.get_stat("exp")
level = self.player_stats.get_stat("level")
# Level up check
exp_needed = level * 100
if exp >= exp_needed:
self.player_stats.set_stat("level", level + 1)
self.player_stats.set_stat("exp", exp - exp_needed)
self.player_stats.set_stat("hp", 100) # Full heal on level up
self.game.speak(f"Level up! Now level {level + 1}!")
def random_encounter(self):
"""Create random encounters."""
self.game.speak("An enemy appears!")
# Add enemy logic here
self.player_stats.update_stat("encounters", 1)
def quick_save(self):
"""Create a quick save."""
try:
save_name = f"Quick Save - Level {self.player_stats.get_stat('level')}"
self.create_complete_save(save_name)
self.game.speak("Game saved!")
except Exception as e:
self.game.speak(f"Save failed: {e}")
def quick_load(self):
"""Load the most recent save."""
try:
saves = self.save_manager.get_save_files()
if saves:
self.load_complete_save(saves[0])
self.game.speak("Game loaded!")
else:
self.game.speak("No saves found!")
except Exception as e:
self.game.speak(f"Load failed: {e}")
def create_complete_save(self, save_name):
"""Create comprehensive save with all systems."""
complete_state = {
"player_stats": self.player_stats.to_dict(),
"weapon": self.player_weapon.to_dict(),
"player_position": (self.player_x, self.player_y),
"current_area": self.current_area,
"difficulty": self.difficulty,
"enemies": [enemy.to_dict() for enemy in self.enemies],
"projectiles": [proj.to_dict() for proj in self.projectiles]
}
metadata = {
"display_name": save_name,
"level": self.player_stats.get_stat("level"),
"location": self.current_area,
"playtime": f"{self.player_stats.get_stat('playtime', from_total=True):.1f}h"
}
return self.save_manager.create_save(complete_state, metadata)
def load_complete_save(self, save_path):
"""Load comprehensive save restoring all systems."""
data, metadata = self.save_manager.load_save(save_path)
# Restore all systems
self.player_stats = sg.StatTracker.from_dict(data["player_stats"])
self.player_weapon = sg.Weapon.from_dict(data["weapon"])
self.player_x, self.player_y = data["player_position"]
self.current_area = data["current_area"]
self.difficulty = data["difficulty"]
# Restore dynamic objects (implementation depends on your enemy/projectile classes)
# self.enemies = [Enemy.from_dict(e) for e in data["enemies"]]
# self.projectiles = [sg.Projectile.from_dict(p) for p in data["projectiles"]]
def settings(self):
options = ["easy", "normal", "hard", "back"]
current = options.index(self.difficulty) if self.difficulty in options else 1
@@ -779,6 +1052,18 @@ if __name__ == "__main__":
- Game title is used to determine configuration directory paths
- Services are interconnected, so proper initialization ensures correct operation
6. **New game systems**:
- Use StatTracker for comprehensive statistics with level/total separation
- Implement SaveManager for reliable save/load with metadata
- Leverage Combat systems for professional weapon and projectile mechanics
- Combine all systems for rich, full-featured games
7. **Performance considerations**:
- Reset level stats regularly to prevent memory bloat
- Clean up old saves periodically using SaveManager methods
- Remove inactive projectiles from update loops
- Use weapon cooldowns to prevent spam attacks
## Troubleshooting