Compare commits

...

8 commits

Author SHA1 Message Date
megaproxy
cd2ad10aec Add comprehensive NPC events system with community collaboration
- Implement NPC events module with full IRC command support:
  - \!events: View all active community events
  - \!event <id>: Get detailed event information and leaderboard
  - \!contribute <id>: Participate in community events
  - \!eventhelp: Comprehensive event system documentation
- Add NPC events backend system with automatic spawning:
  - Configurable event types (resource gathering, pet rescue, exploration)
  - Difficulty levels (easy, medium, hard) with scaled rewards
  - Community collaboration mechanics with shared progress
  - Automatic event spawning and expiration management
- Database integration for event tracking and player contributions
- Expandable system supporting future event types and mechanics
- Admin \!startevent command for manual event creation
- Comprehensive error handling and user feedback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:34:01 +00:00
megaproxy
530134bd36 Update bot and webserver integration for healing system support
- Enhance bot initialization to support healing system background tasks
- Update webserver to properly handle healing system web interfaces
- Ensure proper integration between IRC bot and web components
- Add support for healing system status monitoring and display
- Maintain unified user experience across IRC and web interfaces

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:33:37 +00:00
megaproxy
8ae7da8379 Update module loading and rate limiting for healing system integration
- Add heal command to rate limiting system for proper cooldown enforcement
- Update module initialization to support new healing system components
- Ensure rate limiting properly handles new heal command with user restrictions
- Maintain system security and prevent healing system abuse
- Clean up deprecated connection and backup commands from rate limiter

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:33:23 +00:00
megaproxy
adcd5afd85 Update help documentation with comprehensive pet healing system
- Update inventory section header to "Inventory & Healing System"
- Add \!heal command documentation with 1-hour cooldown details
- Enhance \!use command description to include revive functionality
- Add comprehensive Pet Healing System info box covering:
  - Fainted pet mechanics and restrictions
  - Revive items (50% and 100% HP restoration)
  - Heal command with cooldown system
  - Auto-recovery after 30 minutes to 1 HP
  - Travel permissions with fainted pets
- Add Pet Fainting System section to Battle System:
  - Battle defeat mechanics
  - Available healing options
  - Strategic impact and type advantages
- Update item rarity categories to include Revive (rare) and Max Revive (epic)
- Maintain consistent styling and navigation structure
- Provide complete user guidance for pet health management

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:33:07 +00:00
megaproxy
a333306ad3 Integrate pet fainting tracking into battle system
- Update wild battle defeat handling to mark pets as fainted
- Update gym battle defeat handling to mark pets as fainted
- Add database faint_pet() calls when pets lose battles
- Ensure fainted pets are properly tracked with timestamps
- Both wild and gym battles now consistently handle pet fainting
- Defeated pets are immediately marked as fainted in database
- Maintains existing battle flow while adding fainting mechanics

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:32:38 +00:00
megaproxy
d758d6b924 Add \!heal command and automatic pet recovery background system
- Implement \!heal command with 1-hour cooldown available to all users
- Add comprehensive cooldown tracking with database last_heal_time validation
- Heal command restores all active pets to full health
- Add background pet recovery system to game engine:
  - Automatic 30-minute recovery timer for fainted pets
  - Background task checks every 5 minutes for eligible pets
  - Auto-recovery restores pets to 1 HP after 30 minutes
  - Proper startup/shutdown integration with game engine
- Add pet_recovery_task to game engine with graceful shutdown
- Include detailed logging for recovery operations
- Ensure system resilience with error handling and task cancellation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:32:25 +00:00
megaproxy
72c1098a22 Implement comprehensive pet healing system with revive items and database support
- Add Revive (50% HP) and Max Revive (100% HP) items to config/items.json
- Extend database schema with fainted_at timestamp for pets table
- Add last_heal_time column to players table for heal command cooldown
- Implement comprehensive pet healing database methods:
  - get_fainted_pets(): Retrieve all fainted pets for a player
  - revive_pet(): Restore fainted pet with specified HP
  - faint_pet(): Mark pet as fainted with timestamp
  - get_pets_for_auto_recovery(): Find pets eligible for 30-minute auto-recovery
  - auto_recover_pet(): Automatically restore pet to 1 HP
  - get_active_pets(): Get active pets excluding fainted ones
  - get_player_pets(): Enhanced to filter out fainted pets when active_only=True
- Update inventory module to handle revive and max_revive effects
- Add proper error handling for cases where no fainted pets exist
- Maintain case-insensitive item usage compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 11:32:01 +00:00
megaproxy
fca0423c84 Add comprehensive startup script validation and enhanced pet system
- Enhanced start_petbot.sh with extensive validation and error checking
- Added emoji support to pet species system with database migration
- Expanded pet species from 9 to 33 unique pets with balanced spawn rates
- Improved database integrity validation and orphaned pet detection
- Added comprehensive pre-startup testing and configuration validation
- Enhanced locations with diverse species spawning across all areas
- Added dual-type pets and rarity-based spawn distribution
- Improved startup information display with feature overview
- Added background monitoring and validation systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 00:17:54 +00:00
18 changed files with 3076 additions and 147 deletions

View file

@ -10,7 +10,11 @@
"Bash(pip3 install:*)",
"Bash(apt list:*)",
"Bash(curl:*)",
"Bash(git commit:*)"
"Bash(git commit:*)",
"Bash(sed:*)",
"Bash(grep:*)",
"Bash(pkill:*)",
"Bash(git add:*)"
],
"deny": []
}

View file

@ -48,6 +48,28 @@
"effect_value": 15,
"locations": ["mystic_forest", "enchanted_grove"],
"spawn_rate": 0.03
},
{
"id": 18,
"name": "Revive",
"description": "Revives a fainted pet and restores 50% of its HP",
"rarity": "rare",
"category": "healing",
"effect": "revive",
"effect_value": 50,
"locations": ["all"],
"spawn_rate": 0.005
},
{
"id": 19,
"name": "Max Revive",
"description": "Revives a fainted pet and fully restores its HP",
"rarity": "epic",
"category": "healing",
"effect": "max_revive",
"effect_value": 100,
"locations": ["all"],
"spawn_rate": 0.002
}
],
"battle_items": [

View file

@ -5,9 +5,11 @@
"level_min": 1,
"level_max": 3,
"spawns": [
{"species": "Leafy", "spawn_rate": 0.35, "min_level": 1, "max_level": 2},
{"species": "Flamey", "spawn_rate": 0.35, "min_level": 1, "max_level": 2},
{"species": "Aqua", "spawn_rate": 0.3, "min_level": 1, "max_level": 2}
{"species": "Leafy", "spawn_rate": 0.25, "min_level": 1, "max_level": 2},
{"species": "Flamey", "spawn_rate": 0.25, "min_level": 1, "max_level": 2},
{"species": "Aqua", "spawn_rate": 0.25, "min_level": 1, "max_level": 2},
{"species": "Seedling", "spawn_rate": 0.15, "min_level": 1, "max_level": 2},
{"species": "Furry", "spawn_rate": 0.1, "min_level": 1, "max_level": 3}
]
},
{
@ -16,10 +18,13 @@
"level_min": 2,
"level_max": 6,
"spawns": [
{"species": "Leafy", "spawn_rate": 0.3, "min_level": 2, "max_level": 4},
{"species": "Vinewrap", "spawn_rate": 0.35, "min_level": 3, "max_level": 5},
{"species": "Bloomtail", "spawn_rate": 0.25, "min_level": 4, "max_level": 6},
{"species": "Flamey", "spawn_rate": 0.1, "min_level": 3, "max_level": 4}
{"species": "Leafy", "spawn_rate": 0.2, "min_level": 2, "max_level": 4},
{"species": "Vinewrap", "spawn_rate": 0.25, "min_level": 3, "max_level": 5},
{"species": "Bloomtail", "spawn_rate": 0.2, "min_level": 4, "max_level": 6},
{"species": "Flamey", "spawn_rate": 0.08, "min_level": 3, "max_level": 4},
{"species": "Fernwhisk", "spawn_rate": 0.15, "min_level": 3, "max_level": 5},
{"species": "Furry", "spawn_rate": 0.08, "min_level": 2, "max_level": 4},
{"species": "Mossrock", "spawn_rate": 0.04, "min_level": 5, "max_level": 6}
]
},
{
@ -28,8 +33,11 @@
"level_min": 4,
"level_max": 9,
"spawns": [
{"species": "Sparky", "spawn_rate": 0.6, "min_level": 4, "max_level": 7},
{"species": "Rocky", "spawn_rate": 0.4, "min_level": 5, "max_level": 8}
{"species": "Sparky", "spawn_rate": 0.35, "min_level": 4, "max_level": 7},
{"species": "Rocky", "spawn_rate": 0.25, "min_level": 5, "max_level": 8},
{"species": "Zapper", "spawn_rate": 0.25, "min_level": 4, "max_level": 6},
{"species": "Ember", "spawn_rate": 0.1, "min_level": 4, "max_level": 6},
{"species": "Swiftpaw", "spawn_rate": 0.05, "min_level": 6, "max_level": 8}
]
},
{
@ -38,8 +46,11 @@
"level_min": 6,
"level_max": 12,
"spawns": [
{"species": "Rocky", "spawn_rate": 0.7, "min_level": 6, "max_level": 10},
{"species": "Sparky", "spawn_rate": 0.3, "min_level": 7, "max_level": 9}
{"species": "Rocky", "spawn_rate": 0.4, "min_level": 6, "max_level": 10},
{"species": "Sparky", "spawn_rate": 0.2, "min_level": 7, "max_level": 9},
{"species": "Pebble", "spawn_rate": 0.25, "min_level": 6, "max_level": 8},
{"species": "Crystalback", "spawn_rate": 0.1, "min_level": 9, "max_level": 12},
{"species": "Voltmane", "spawn_rate": 0.05, "min_level": 10, "max_level": 12}
]
},
{
@ -48,9 +59,13 @@
"level_min": 10,
"level_max": 16,
"spawns": [
{"species": "Hydrox", "spawn_rate": 0.4, "min_level": 10, "max_level": 14},
{"species": "Rocky", "spawn_rate": 0.3, "min_level": 11, "max_level": 15},
{"species": "Sparky", "spawn_rate": 0.3, "min_level": 12, "max_level": 14}
{"species": "Hydrox", "spawn_rate": 0.25, "min_level": 10, "max_level": 14},
{"species": "Rocky", "spawn_rate": 0.2, "min_level": 11, "max_level": 15},
{"species": "Sparky", "spawn_rate": 0.15, "min_level": 12, "max_level": 14},
{"species": "Snowball", "spawn_rate": 0.2, "min_level": 10, "max_level": 12},
{"species": "Frostbite", "spawn_rate": 0.1, "min_level": 12, "max_level": 15},
{"species": "Bubblin", "spawn_rate": 0.05, "min_level": 10, "max_level": 13},
{"species": "Frostleaf", "spawn_rate": 0.05, "min_level": 14, "max_level": 16}
]
},
{
@ -59,9 +74,19 @@
"level_min": 15,
"level_max": 25,
"spawns": [
{"species": "Blazeon", "spawn_rate": 0.5, "min_level": 15, "max_level": 20},
{"species": "Hydrox", "spawn_rate": 0.3, "min_level": 16, "max_level": 22},
{"species": "Rocky", "spawn_rate": 0.2, "min_level": 18, "max_level": 25}
{"species": "Blazeon", "spawn_rate": 0.22, "min_level": 15, "max_level": 20},
{"species": "Hydrox", "spawn_rate": 0.18, "min_level": 16, "max_level": 22},
{"species": "Rocky", "spawn_rate": 0.13, "min_level": 18, "max_level": 25},
{"species": "Scorchclaw", "spawn_rate": 0.07, "min_level": 15, "max_level": 18},
{"species": "Tidalfin", "spawn_rate": 0.07, "min_level": 16, "max_level": 19},
{"species": "Infernowyrm", "spawn_rate": 0.05, "min_level": 20, "max_level": 25},
{"species": "Abyssal", "spawn_rate": 0.05, "min_level": 20, "max_level": 25},
{"species": "Thornking", "spawn_rate": 0.05, "min_level": 20, "max_level": 25},
{"species": "Stormcaller", "spawn_rate": 0.05, "min_level": 20, "max_level": 25},
{"species": "Steamvent", "spawn_rate": 0.04, "min_level": 19, "max_level": 23},
{"species": "Mountainlord", "spawn_rate": 0.03, "min_level": 22, "max_level": 25},
{"species": "Glaciarch", "spawn_rate": 0.03, "min_level": 22, "max_level": 25},
{"species": "Harmonix", "spawn_rate": 0.03, "min_level": 18, "max_level": 22}
]
}
]

View file

@ -8,7 +8,8 @@
"base_defense": 43,
"base_speed": 65,
"evolution_level": null,
"rarity": 1
"rarity": 1,
"emoji": "🔥"
},
{
"name": "Aqua",
@ -19,7 +20,8 @@
"base_defense": 65,
"base_speed": 43,
"evolution_level": null,
"rarity": 1
"rarity": 1,
"emoji": "💧"
},
{
"name": "Leafy",
@ -30,7 +32,8 @@
"base_defense": 49,
"base_speed": 45,
"evolution_level": null,
"rarity": 1
"rarity": 1,
"emoji": "🍃"
},
{
"name": "Sparky",
@ -41,7 +44,8 @@
"base_defense": 40,
"base_speed": 90,
"evolution_level": null,
"rarity": 2
"rarity": 2,
"emoji": "⚡"
},
{
"name": "Rocky",
@ -52,7 +56,8 @@
"base_defense": 100,
"base_speed": 25,
"evolution_level": null,
"rarity": 2
"rarity": 2,
"emoji": "🗿"
},
{
"name": "Blazeon",
@ -63,7 +68,8 @@
"base_defense": 60,
"base_speed": 95,
"evolution_level": null,
"rarity": 3
"rarity": 3,
"emoji": "🌋"
},
{
"name": "Hydrox",
@ -74,7 +80,8 @@
"base_defense": 90,
"base_speed": 60,
"evolution_level": null,
"rarity": 3
"rarity": 3,
"emoji": "🌊"
},
{
"name": "Vinewrap",
@ -85,7 +92,8 @@
"base_defense": 70,
"base_speed": 40,
"evolution_level": null,
"rarity": 2
"rarity": 2,
"emoji": "🌿"
},
{
"name": "Bloomtail",
@ -96,6 +104,295 @@
"base_defense": 50,
"base_speed": 80,
"evolution_level": null,
"rarity": 2
"rarity": 2,
"emoji": "🌺"
},
{
"name": "Ember",
"type1": "Fire",
"type2": null,
"base_hp": 42,
"base_attack": 50,
"base_defense": 40,
"base_speed": 68,
"evolution_level": null,
"rarity": 1,
"emoji": "✨"
},
{
"name": "Scorchclaw",
"type1": "Fire",
"type2": null,
"base_hp": 55,
"base_attack": 75,
"base_defense": 55,
"base_speed": 70,
"evolution_level": null,
"rarity": 2,
"emoji": "🐱"
},
{
"name": "Infernowyrm",
"type1": "Fire",
"type2": null,
"base_hp": 90,
"base_attack": 120,
"base_defense": 75,
"base_speed": 85,
"evolution_level": null,
"rarity": 4,
"emoji": "🐉"
},
{
"name": "Bubblin",
"type1": "Water",
"type2": null,
"base_hp": 48,
"base_attack": 40,
"base_defense": 60,
"base_speed": 52,
"evolution_level": null,
"rarity": 1,
"emoji": "🫧"
},
{
"name": "Tidalfin",
"type1": "Water",
"type2": null,
"base_hp": 65,
"base_attack": 60,
"base_defense": 70,
"base_speed": 80,
"evolution_level": null,
"rarity": 2,
"emoji": "🐬"
},
{
"name": "Abyssal",
"type1": "Water",
"type2": null,
"base_hp": 100,
"base_attack": 85,
"base_defense": 110,
"base_speed": 55,
"evolution_level": null,
"rarity": 4,
"emoji": "🐙"
},
{
"name": "Seedling",
"type1": "Grass",
"type2": null,
"base_hp": 40,
"base_attack": 35,
"base_defense": 50,
"base_speed": 40,
"evolution_level": null,
"rarity": 1,
"emoji": "🌱"
},
{
"name": "Fernwhisk",
"type1": "Grass",
"type2": null,
"base_hp": 50,
"base_attack": 55,
"base_defense": 65,
"base_speed": 75,
"evolution_level": null,
"rarity": 2,
"emoji": "🌾"
},
{
"name": "Thornking",
"type1": "Grass",
"type2": null,
"base_hp": 85,
"base_attack": 95,
"base_defense": 120,
"base_speed": 50,
"evolution_level": null,
"rarity": 4,
"emoji": "👑"
},
{
"name": "Zapper",
"type1": "Electric",
"type2": null,
"base_hp": 30,
"base_attack": 45,
"base_defense": 35,
"base_speed": 95,
"evolution_level": null,
"rarity": 1,
"emoji": "🐭"
},
{
"name": "Voltmane",
"type1": "Electric",
"type2": null,
"base_hp": 60,
"base_attack": 85,
"base_defense": 50,
"base_speed": 110,
"evolution_level": null,
"rarity": 3,
"emoji": "🐎"
},
{
"name": "Stormcaller",
"type1": "Electric",
"type2": null,
"base_hp": 75,
"base_attack": 130,
"base_defense": 60,
"base_speed": 125,
"evolution_level": null,
"rarity": 4,
"emoji": "🦅"
},
{
"name": "Pebble",
"type1": "Rock",
"type2": null,
"base_hp": 45,
"base_attack": 60,
"base_defense": 80,
"base_speed": 20,
"evolution_level": null,
"rarity": 1,
"emoji": "🪨"
},
{
"name": "Crystalback",
"type1": "Rock",
"type2": null,
"base_hp": 70,
"base_attack": 90,
"base_defense": 130,
"base_speed": 35,
"evolution_level": null,
"rarity": 3,
"emoji": "🐢"
},
{
"name": "Mountainlord",
"type1": "Rock",
"type2": null,
"base_hp": 120,
"base_attack": 110,
"base_defense": 150,
"base_speed": 20,
"evolution_level": null,
"rarity": 4,
"emoji": "⛰️"
},
{
"name": "Snowball",
"type1": "Ice",
"type2": null,
"base_hp": 40,
"base_attack": 35,
"base_defense": 55,
"base_speed": 45,
"evolution_level": null,
"rarity": 1,
"emoji": "☃️"
},
{
"name": "Frostbite",
"type1": "Ice",
"type2": null,
"base_hp": 55,
"base_attack": 65,
"base_defense": 70,
"base_speed": 85,
"evolution_level": null,
"rarity": 2,
"emoji": "🦨"
},
{
"name": "Glaciarch",
"type1": "Ice",
"type2": null,
"base_hp": 95,
"base_attack": 80,
"base_defense": 130,
"base_speed": 45,
"evolution_level": null,
"rarity": 4,
"emoji": "❄️"
},
{
"name": "Furry",
"type1": "Normal",
"type2": null,
"base_hp": 50,
"base_attack": 45,
"base_defense": 45,
"base_speed": 60,
"evolution_level": null,
"rarity": 1,
"emoji": "🐹"
},
{
"name": "Swiftpaw",
"type1": "Normal",
"type2": null,
"base_hp": 55,
"base_attack": 70,
"base_defense": 50,
"base_speed": 100,
"evolution_level": null,
"rarity": 2,
"emoji": "🐺"
},
{
"name": "Harmonix",
"type1": "Normal",
"type2": null,
"base_hp": 80,
"base_attack": 75,
"base_defense": 75,
"base_speed": 80,
"evolution_level": null,
"rarity": 3,
"emoji": "🎵"
},
{
"name": "Steamvent",
"type1": "Water",
"type2": "Fire",
"base_hp": 65,
"base_attack": 80,
"base_defense": 70,
"base_speed": 75,
"evolution_level": null,
"rarity": 3,
"emoji": "💨"
},
{
"name": "Mossrock",
"type1": "Grass",
"type2": "Rock",
"base_hp": 70,
"base_attack": 65,
"base_defense": 100,
"base_speed": 40,
"evolution_level": null,
"rarity": 3,
"emoji": "🍄"
},
{
"name": "Frostleaf",
"type1": "Ice",
"type2": "Grass",
"base_hp": 60,
"base_attack": 55,
"base_defense": 85,
"base_speed": 65,
"evolution_level": null,
"rarity": 3,
"emoji": "🧊"
}
]

366
help.html
View file

@ -214,6 +214,112 @@
.gym-card strong {
color: var(--text-accent);
}
/* Quick Navigation */
.quick-nav {
background: var(--bg-secondary);
border-radius: 15px;
padding: 20px;
margin-bottom: 30px;
box-shadow: var(--shadow-dark);
border: 1px solid var(--border-color);
}
.quick-nav h3 {
color: var(--text-accent);
margin: 0 0 15px 0;
font-size: 1.2em;
text-align: center;
}
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.nav-item {
background: var(--bg-tertiary);
padding: 12px 16px;
border-radius: 8px;
text-align: center;
transition: all 0.3s ease;
border: 1px solid var(--border-color);
}
.nav-item:hover {
background: var(--hover-color);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 255, 102, 0.15);
}
.nav-item a {
color: var(--text-primary);
text-decoration: none;
font-weight: 500;
font-size: 0.9em;
display: block;
}
.nav-item a:hover {
color: var(--text-accent);
}
/* Smooth scrolling for anchor links */
html {
scroll-behavior: smooth;
}
/* Section anchor targets */
.section {
scroll-margin-top: 20px;
}
/* Back to top button */
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
background: var(--gradient-primary);
color: white;
border: none;
border-radius: 50%;
font-size: 18px;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
.back-to-top.show {
opacity: 1;
visibility: visible;
}
.back-to-top:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
/* Mobile responsiveness for navigation */
@media (max-width: 768px) {
.nav-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.nav-item {
padding: 10px 12px;
}
.nav-item a {
font-size: 0.8em;
}
}
</style>
</head>
<body>
@ -224,7 +330,25 @@
<p>Complete guide to Pokemon-style pet collecting in IRC</p>
</div>
<div class="section">
<div class="quick-nav">
<h3>🧭 Quick Navigation</h3>
<div class="nav-grid">
<div class="nav-item"><a href="#getting-started">🚀 Getting Started</a></div>
<div class="nav-item"><a href="#exploration">🌍 Exploration</a></div>
<div class="nav-item"><a href="#battle-system">⚔️ Battle System</a></div>
<div class="nav-item"><a href="#gym-battles">🏛️ Gym Battles</a></div>
<div class="nav-item"><a href="#pet-management">🐾 Pet Management</a></div>
<div class="nav-item"><a href="#inventory">🎒 Inventory</a></div>
<div class="nav-item"><a href="#community-events">🎯 Community Events</a></div>
<div class="nav-item"><a href="#achievements">🏆 Achievements</a></div>
<div class="nav-item"><a href="#web-interface">🌐 Web Interface</a></div>
<div class="nav-item"><a href="#bot-status">🤖 Bot Status</a></div>
<div class="nav-item"><a href="#rate-limiting">⚡ Rate Limiting</a></div>
<div class="nav-item"><a href="#admin-commands">🛡️ Admin Commands</a></div>
</div>
</div>
<div class="section" id="getting-started">
<div class="section-header">🚀 Getting Started</div>
<div class="section-content">
<div class="command-grid">
@ -247,7 +371,7 @@
</div>
</div>
<div class="section">
<div class="section" id="exploration">
<div class="section-header">🌍 Exploration & Travel</div>
<div class="section-content">
<div class="command-grid">
@ -271,6 +395,11 @@
<div class="command-desc">See which location you're currently in and get information about the area.</div>
<div class="command-example">Example: !where</div>
</div>
<div class="command">
<div class="command-name">!wild [location]</div>
<div class="command-desc">Show wild pets available in your current location or a specified location. Helps you see what pets you can encounter.</div>
<div class="command-example">Example: !wild or !wild crystal caves</div>
</div>
</div>
<div class="info-box">
@ -299,7 +428,7 @@
</div>
</div>
<div class="section">
<div class="section" id="battle-system">
<div class="section-header">⚔️ Battle System</div>
<div class="section-content">
<div class="command-grid">
@ -329,10 +458,20 @@
<div class="command-example">Example: !flee</div>
</div>
</div>
<div class="info-box">
<h4>💀 Pet Fainting System</h4>
<ul>
<li><strong>Battle Defeat</strong> - Pets that lose battles will faint and cannot be used until healed</li>
<li><strong>Healing Options</strong> - Use Revive items, !heal command, or wait 30 minutes for auto-recovery</li>
<li><strong>Strategic Impact</strong> - Plan your battles carefully to avoid having all pets faint</li>
<li><strong>Type Advantages</strong> - Use type matchups to win battles and avoid fainting</li>
</ul>
</div>
</div>
</div>
<div class="section">
<div class="section" id="gym-battles">
<div class="section-header">🏛️ Gym Battles <span class="badge">NEW!</span></div>
<div class="section-content">
<div class="command-grid">
@ -356,6 +495,11 @@
<div class="command-desc">Get detailed information about a gym including leader, theme, team, and badge details.</div>
<div class="command-example">Example: !gym info "Storm Master"</div>
</div>
<div class="command">
<div class="command-name">!forfeit</div>
<div class="command-desc">Forfeit your current gym battle if you're losing or want to try a different strategy.</div>
<div class="command-example">Example: !forfeit</div>
</div>
</div>
<div class="tip">
@ -406,7 +550,7 @@
</div>
</div>
<div class="section">
<div class="section" id="pet-management">
<div class="section-header">🐾 Pet Management</div>
<div class="section-content">
<div class="command-grid">
@ -430,12 +574,17 @@
<div class="command-desc">Remove a pet from your active team and put it in storage.</div>
<div class="command-example">Example: !deactivate aqua</div>
</div>
<div class="command">
<div class="command-name">!nickname &lt;pet&gt; &lt;new_nickname&gt;</div>
<div class="command-desc">Give a custom nickname to one of your pets. Use their current name or ID to reference them.</div>
<div class="command-example">Example: !nickname flamey "Blazer"</div>
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-header">🎒 Inventory System <span class="badge">NEW!</span></div>
<div class="section" id="inventory">
<div class="section-header">🎒 Inventory & Healing System <span class="badge">UPDATED!</span></div>
<div class="section-content">
<div class="command-grid">
<div class="command">
@ -445,8 +594,13 @@
</div>
<div class="command">
<div class="command-name">!use &lt;item name&gt;</div>
<div class="command-desc">Use a consumable item from your inventory. Items can heal pets, boost stats, or provide other benefits.</div>
<div class="command-example">Example: !use Small Potion</div>
<div class="command-desc">Use a consumable item from your inventory. Items can heal pets, boost stats, revive fainted pets, or provide other benefits.</div>
<div class="command-example">Example: !use Small Potion, !use Revive</div>
</div>
<div class="command">
<div class="command-name">!heal</div>
<div class="command-desc">Heal all your active pets to full health. Has a 1-hour cooldown to prevent abuse.</div>
<div class="command-example">Example: !heal</div>
</div>
</div>
@ -455,8 +609,8 @@
<ul>
<li><strong>○ Common (15%)</strong> - Small Potions, basic healing items</li>
<li><strong>◇ Uncommon (8-12%)</strong> - Large Potions, battle boosters, special berries</li>
<li><strong>◆ Rare (3-6%)</strong> - Super Potions, speed elixirs, location treasures</li>
<li><strong>★ Epic (2-3%)</strong> - Evolution stones, rare crystals, ancient artifacts</li>
<li><strong>◆ Rare (3-6%)</strong> - Super Potions, Revive items, speed elixirs, location treasures</li>
<li><strong>★ Epic (2-3%)</strong> - Max Revive, evolution stones, rare crystals, ancient artifacts</li>
<li><strong>✦ Legendary (1%)</strong> - Lucky charms, ancient fossils, ultimate items</li>
</ul>
</div>
@ -464,9 +618,74 @@
<div class="tip">
💡 <strong>Item Discovery:</strong> Find items while exploring! Each location has unique treasures. Items stack in your inventory and can be used anytime.
</div>
<div class="info-box">
<h4>🏥 Pet Healing System</h4>
<ul>
<li><strong>Fainted Pets</strong> - Pets that lose battles faint and cannot be used until healed</li>
<li><strong>Revive Items</strong> - Use Revive (50% HP) or Max Revive (100% HP) to restore fainted pets</li>
<li><strong>!heal Command</strong> - Heals all active pets to full health (1-hour cooldown)</li>
<li><strong>Auto-Recovery</strong> - Fainted pets automatically recover to 1 HP after 30 minutes</li>
<li><strong>Travel Allowed</strong> - You can still travel and explore with fainted pets</li>
</ul>
</div>
</div>
<div class="section">
<div class="section" id="community-events">
<div class="section-header">🎯 Community Events <span class="badge">NEW!</span></div>
<div class="section-content">
<div class="command-grid">
<div class="command">
<div class="command-name">!events</div>
<div class="command-desc">View all active community events that all players can participate in together.</div>
<div class="command-example">Example: !events</div>
</div>
<div class="command">
<div class="command-name">!event &lt;id&gt;</div>
<div class="command-desc">Get detailed information about a specific community event, including progress and leaderboard.</div>
<div class="command-example">Example: !event 1</div>
</div>
<div class="command">
<div class="command-name">!contribute &lt;id&gt;</div>
<div class="command-desc">Contribute to a community event. Everyone who participates gets rewards when the event completes!</div>
<div class="command-example">Example: !contribute 1</div>
</div>
<div class="command">
<div class="command-name">!eventhelp</div>
<div class="command-desc">Get detailed help about the community events system and how it works.</div>
<div class="command-example">Example: !eventhelp</div>
</div>
</div>
<div class="info-box">
<h4>🤝 How Community Events Work</h4>
<ul>
<li><strong>Collaborative Goals</strong> - All players work together toward shared objectives</li>
<li><strong>Progress Tracking</strong> - Events show progress bars and contribution leaderboards</li>
<li><strong>Time Limited</strong> - Events have deadlines and expire if not completed</li>
<li><strong>Difficulty Levels</strong> - ⭐ Easy, ⭐⭐ Medium, ⭐⭐⭐ Hard events with better rewards</li>
<li><strong>Automatic Spawning</strong> - New events appear regularly for ongoing engagement</li>
</ul>
</div>
<div class="info-box">
<h4>🎪 Event Types</h4>
<ul>
<li><strong>🏪 Resource Gathering</strong> - Help collect supplies for the community</li>
<li><strong>🐾 Pet Rescue</strong> - Search for and rescue missing pets</li>
<li><strong>🎪 Community Projects</strong> - Work together on town improvement projects</li>
<li><strong>🚨 Emergency Response</strong> - Help during natural disasters or crises</li>
<li><strong>🔬 Research</strong> - Assist scientists with important discoveries</li>
</ul>
</div>
<div class="tip">
💡 <strong>Event Strategy:</strong> Check !events regularly for new opportunities! Higher difficulty events give better rewards, and contributing more increases your reward multiplier when the event completes.
</div>
</div>
</div>
<div class="section" id="achievements">
<div class="section-header">🏆 Achievements & Progress</div>
<div class="section-content">
<div class="command-grid">
@ -489,7 +708,7 @@
</div>
</div>
<div class="section">
<div class="section" id="web-interface">
<div class="section-header">🌐 Web Interface</div>
<div class="section-content">
<div class="tip">
@ -499,12 +718,47 @@
<li><strong>Leaderboard</strong> - Top players by level and achievements</li>
<li><strong>Locations Guide</strong> - All areas with spawn information</li>
<li><strong>Gym Badges</strong> - Display your earned badges and progress</li>
<li><strong>Interactive Map</strong> - See where all players are exploring</li>
<li><strong>Team Builder</strong> - Drag-and-drop team management with PIN verification</li>
</ul>
</div>
</div>
</div>
<div class="section">
<div class="section" id="bot-status">
<div class="section-header">🤖 Bot Status & Utilities</div>
<div class="section-content">
<div class="command-grid">
<div class="command">
<div class="command-name">!status</div>
<div class="command-desc">Check the bot's current connection status and basic system information.</div>
<div class="command-example">Example: !status</div>
</div>
<div class="command">
<div class="command-name">!uptime</div>
<div class="command-desc">See how long the bot has been running since last restart.</div>
<div class="command-example">Example: !uptime</div>
</div>
<div class="command">
<div class="command-name">!ping</div>
<div class="command-desc">Test the bot's responsiveness with a simple ping-pong test.</div>
<div class="command-example">Example: !ping</div>
</div>
</div>
<div class="info-box">
<h4>🔧 System Status</h4>
<ul>
<li><strong>Connection Monitoring</strong> - Bot automatically monitors its IRC connection</li>
<li><strong>Auto-Reconnect</strong> - Automatically reconnects if connection is lost</li>
<li><strong>Background Tasks</strong> - Weather updates, event spawning, and data validation</li>
<li><strong>Rate Limiting</strong> - Built-in protection against spam and abuse</li>
</ul>
</div>
</div>
</div>
<div class="section" id="rate-limiting">
<div class="section-header">⚡ Rate Limiting & Fair Play</div>
<div class="section-content">
<div class="info-box">
@ -573,12 +827,92 @@
</div>
</div>
<div class="section" id="admin-commands">
<div class="section-header">🛡️ Admin Commands</div>
<div class="section-content">
<div class="command-grid">
<div class="command">
<div class="command-name">!reload</div>
<div class="command-desc">Reload all bot modules without restarting the bot (Admin only).</div>
<div class="command-example">Example: !reload</div>
</div>
<div class="command">
<div class="command-name">!setweather &lt;location|all&gt; &lt;weather_type&gt; [duration]</div>
<div class="command-desc">Manually set weather for a location or all locations (Admin only).</div>
<div class="command-example">Example: !setweather all sunny 120</div>
</div>
<div class="command">
<div class="command-name">!spawnevent [difficulty]</div>
<div class="command-desc">Force spawn a community event with optional difficulty 1-3 (Admin only).</div>
<div class="command-example">Example: !spawnevent 2</div>
</div>
<div class="command">
<div class="command-name">!startevent [type] [difficulty]</div>
<div class="command-desc">Start a specific event type. Without args, shows available types (Admin only).</div>
<div class="command-example">Example: !startevent resource_gathering 2</div>
</div>
</div>
<div class="info-box">
<h4>🔑 Admin Access</h4>
<ul>
<li><strong>Single Admin User</strong> - Only one designated admin user can use these commands</li>
<li><strong>Module Management</strong> - Reload modules without restarting the entire bot</li>
<li><strong>Weather Control</strong> - Force weather changes for testing or events</li>
<li><strong>Event Management</strong> - Spawn community events on demand</li>
</ul>
</div>
<div class="tip">
⚠️ <strong>Admin Note:</strong> These commands require admin privileges and can affect the entire bot system. Use with caution and always test changes in a development environment first.
</div>
</div>
</div>
<div class="footer">
<p><strong>🎮 PetBot v0.2.0</strong> - Pokemon-style pet collecting for IRC</p>
<p>Catch pets • Battle gyms • Collect items • Earn badges • Explore locations</p>
<p>Catch pets • Battle gyms • Collect items • Earn badges • Explore locations • Join community events</p>
<p style="margin-top: 15px; opacity: 0.7;">
Need help? Ask in the channel or visit the web dashboard for detailed information!
Need help? Ask in the channel or visit the web dashboard for detailed information!<br>
<strong>New Features:</strong> Community Events • Interactive Map • Team Builder • Enhanced Admin Tools
</p>
</div>
<!-- Back to Top Button -->
<button class="back-to-top" id="backToTop" onclick="scrollToTop()"></button>
<script>
// Show/hide back to top button
window.addEventListener('scroll', function() {
const backToTopBtn = document.getElementById('backToTop');
if (window.pageYOffset > 300) {
backToTopBtn.classList.add('show');
} else {
backToTopBtn.classList.remove('show');
}
});
// Smooth scroll to top function
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
// Add smooth scrolling to all navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
</script>
</body>
</html>

View file

@ -10,6 +10,7 @@ from .admin import Admin
from .inventory import Inventory
from .gym_battles import GymBattles
from .team_builder import TeamBuilder
from .npc_events import NPCEventsModule
__all__ = [
'CoreCommands',
@ -20,5 +21,6 @@ __all__ = [
'Admin',
'Inventory',
'GymBattles',
'TeamBuilder'
'TeamBuilder',
'NPCEventsModule'
]

View file

@ -21,7 +21,7 @@ class Admin(BaseModule):
"""Handles admin-only commands like reload"""
def get_commands(self):
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset", "weather", "setweather"]
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset", "weather", "setweather", "spawnevent", "startevent", "status", "uptime", "ping", "heal"]
async def handle_command(self, channel, nickname, command, args):
if command == "reload":
@ -38,6 +38,18 @@ class Admin(BaseModule):
await self.cmd_weather(channel, nickname, args)
elif command == "setweather":
await self.cmd_setweather(channel, nickname, args)
elif command == "spawnevent":
await self.cmd_spawnevent(channel, nickname, args)
elif command == "startevent":
await self.cmd_startevent(channel, nickname, args)
elif command == "status":
await self.cmd_status(channel, nickname)
elif command == "uptime":
await self.cmd_uptime(channel, nickname)
elif command == "ping":
await self.cmd_ping(channel, nickname)
elif command == "heal":
await self.cmd_heal(channel, nickname)
async def cmd_reload(self, channel, nickname):
"""Reload bot modules (admin only)"""
@ -317,4 +329,182 @@ class Admin(BaseModule):
except ValueError as e:
self.send_message(channel, f"{nickname}: ❌ Invalid duration: {str(e)}")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error setting weather: {str(e)}")
self.send_message(channel, f"{nickname}: ❌ Error setting weather: {str(e)}")
async def cmd_spawnevent(self, channel, nickname, args):
"""Force spawn an NPC event (admin only)"""
if not self.is_admin(nickname):
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
return
# Default to difficulty 1 if no args provided
difficulty = 1
if args:
try:
difficulty = int(args[0])
if difficulty not in [1, 2, 3]:
self.send_message(channel, f"{nickname}: ❌ Difficulty must be 1, 2, or 3.")
return
except ValueError:
self.send_message(channel, f"{nickname}: ❌ Invalid difficulty. Use 1, 2, or 3.")
return
try:
# Get the NPC events manager from the bot
if hasattr(self.bot, 'npc_events') and self.bot.npc_events:
event_id = await self.bot.npc_events.force_spawn_event(difficulty)
if event_id:
self.send_message(channel, f"🎯 {nickname}: Spawned new NPC event! Check `!events` to see it.")
else:
self.send_message(channel, f"{nickname}: Failed to spawn NPC event.")
else:
self.send_message(channel, f"{nickname}: NPC events system not available.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error spawning event: {str(e)}")
async def cmd_startevent(self, channel, nickname, args):
"""Start a specific event type (admin only)"""
if not self.is_admin(nickname):
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
return
# If no args provided, show available event types
if not args:
self.send_message(channel, f"{nickname}: Available types: resource_gathering, pet_rescue, community_project, emergency_response, festival_preparation, research_expedition, crisis_response, legendary_encounter, ancient_mystery")
return
event_type = args[0].lower()
valid_types = ["resource_gathering", "pet_rescue", "community_project", "emergency_response",
"festival_preparation", "research_expedition", "crisis_response",
"legendary_encounter", "ancient_mystery"]
if event_type not in valid_types:
self.send_message(channel, f"{nickname}: ❌ Invalid type. Available: {', '.join(valid_types)}")
return
# Optional difficulty parameter
difficulty = 1
if len(args) > 1:
try:
difficulty = int(args[1])
if difficulty not in [1, 2, 3]:
self.send_message(channel, f"{nickname}: ❌ Difficulty must be 1, 2, or 3.")
return
except ValueError:
self.send_message(channel, f"{nickname}: ❌ Invalid difficulty. Use 1, 2, or 3.")
return
try:
# Get the NPC events manager from the bot
if hasattr(self.bot, 'npc_events') and self.bot.npc_events:
event_id = await self.bot.npc_events.force_spawn_specific_event(event_type, difficulty)
if event_id:
self.send_message(channel, f"🎯 {nickname}: Started {event_type} event (ID: {event_id})! Check `!events` to see it.")
else:
self.send_message(channel, f"{nickname}: Failed to start {event_type} event.")
else:
self.send_message(channel, f"{nickname}: NPC events system not available.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error starting event: {str(e)}")
async def cmd_status(self, channel, nickname):
"""Show bot connection status (available to all users)"""
try:
# Check if connection manager exists
if hasattr(self.bot, 'connection_manager') and self.bot.connection_manager:
stats = self.bot.connection_manager.get_connection_stats()
connected = stats.get('connected', False)
state = stats.get('state', 'unknown')
status_emoji = "🟢" if connected else "🔴"
self.send_message(channel, f"{status_emoji} {nickname}: Bot status - {state.upper()}")
else:
self.send_message(channel, f"🟢 {nickname}: Bot is running")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error getting status: {str(e)}")
async def cmd_uptime(self, channel, nickname):
"""Show bot uptime (available to all users)"""
try:
# Check if bot has startup time
if hasattr(self.bot, 'startup_time'):
import datetime
uptime = datetime.datetime.now() - self.bot.startup_time
days = uptime.days
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if days > 0:
uptime_str = f"{days}d {hours}h {minutes}m"
elif hours > 0:
uptime_str = f"{hours}h {minutes}m"
else:
uptime_str = f"{minutes}m {seconds}s"
self.send_message(channel, f"⏱️ {nickname}: Bot uptime - {uptime_str}")
else:
self.send_message(channel, f"⏱️ {nickname}: Bot is running (uptime unknown)")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error getting uptime: {str(e)}")
async def cmd_ping(self, channel, nickname):
"""Test bot responsiveness (available to all users)"""
try:
import time
start_time = time.time()
# Simple responsiveness test
response_time = (time.time() - start_time) * 1000 # Convert to milliseconds
self.send_message(channel, f"🏓 {nickname}: Pong! Response time: {response_time:.1f}ms")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error with ping: {str(e)}")
async def cmd_heal(self, channel, nickname):
"""Heal active pets (available to all users with 1-hour cooldown)"""
try:
player = await self.require_player(channel, nickname)
if not player:
return
# Check cooldown
from datetime import datetime, timedelta
last_heal = await self.database.get_last_heal_time(player["id"])
if last_heal:
time_since_heal = datetime.now() - last_heal
if time_since_heal < timedelta(hours=1):
remaining = timedelta(hours=1) - time_since_heal
minutes_remaining = int(remaining.total_seconds() / 60)
self.send_message(channel, f"{nickname}: Heal command is on cooldown! {minutes_remaining} minutes remaining.")
return
# Get active pets
active_pets = await self.database.get_active_pets(player["id"])
if not active_pets:
self.send_message(channel, f"{nickname}: You don't have any active pets to heal!")
return
# Count how many pets need healing
pets_healed = 0
for pet in active_pets:
if pet["hp"] < pet["max_hp"]:
# Heal pet to full HP
await self.database.update_pet_hp(pet["id"], pet["max_hp"])
pets_healed += 1
if pets_healed == 0:
self.send_message(channel, f"{nickname}: All your active pets are already at full health!")
return
# Update cooldown
await self.database.update_last_heal_time(player["id"])
self.send_message(channel, f"💊 {nickname}: Healed {pets_healed} pet{'s' if pets_healed != 1 else ''} to full health! Next heal available in 1 hour.")
except Exception as e:
self.send_message(channel, f"{nickname}: ❌ Error with heal command: {str(e)}")

View file

@ -148,6 +148,12 @@ class BattleSystem(BaseModule):
del self.bot.active_encounters[player["id"]]
else:
self.send_message(channel, f"💀 {nickname}: Your pet fainted! You lost the battle...")
# Mark pet as fainted in database
pets = await self.database.get_player_pets(player["id"], active_only=True)
if pets:
await self.database.faint_pet(pets[0]["id"])
# Remove encounter
if player["id"] in self.bot.active_encounters:
del self.bot.active_encounters[player["id"]]
@ -293,6 +299,11 @@ class BattleSystem(BaseModule):
# Player lost gym battle
result = await self.database.end_gym_battle(player["id"], victory=False)
# Mark pet as fainted in database
pets = await self.database.get_player_pets(player["id"], active_only=True)
if pets:
await self.database.faint_pet(pets[0]["id"])
self.send_message(channel, f"💀 {nickname}: Your pet fainted!")
self.send_message(channel,
f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Good battle! Train more and come back stronger!\"")

View file

@ -99,6 +99,41 @@ class Inventory(BaseModule):
self.send_message(channel,
f"🍀 {nickname}: Used {item['name']}! Rare pet encounter rate increased by {effect_value}% for 1 hour!")
elif effect == "revive":
# Handle revive items for fainted pets
fainted_pets = await self.database.get_fainted_pets(player["id"])
if not fainted_pets:
self.send_message(channel, f"{nickname}: You don't have any fainted pets to revive!")
return
# Use the first fainted pet (can be expanded to choose specific pet)
pet = fainted_pets[0]
new_hp = int(pet["max_hp"] * (effect_value / 100.0)) # Convert percentage to HP
# Revive the pet and restore HP
await self.database.revive_pet(pet["id"], new_hp)
self.send_message(channel,
f"💫 {nickname}: Used {item['name']} on {pet['nickname'] or pet['species_name']}! "
f"Revived and restored {new_hp}/{pet['max_hp']} HP!")
elif effect == "max_revive":
# Handle max revive items for fainted pets
fainted_pets = await self.database.get_fainted_pets(player["id"])
if not fainted_pets:
self.send_message(channel, f"{nickname}: You don't have any fainted pets to revive!")
return
# Use the first fainted pet (can be expanded to choose specific pet)
pet = fainted_pets[0]
# Revive the pet and fully restore HP
await self.database.revive_pet(pet["id"], pet["max_hp"])
self.send_message(channel,
f"{nickname}: Used {item['name']} on {pet['nickname'] or pet['species_name']}! "
f"Revived and fully restored HP! ({pet['max_hp']}/{pet['max_hp']})")
elif effect == "money":
# Handle money items (like Coin Pouch)
import random

236
modules/npc_events.py Normal file
View file

@ -0,0 +1,236 @@
"""
NPC Events Module
Handles player commands for NPC events system
"""
from modules.base_module import BaseModule
from src.npc_events import NPCEventsManager
from datetime import datetime
class NPCEventsModule(BaseModule):
"""Module for NPC events system commands"""
def __init__(self, bot, database, game_engine):
super().__init__(bot, database, game_engine)
self.events_manager = NPCEventsManager(database)
def get_commands(self):
return ['events', 'event', 'help', 'contribute', 'eventhelp']
async def handle_command(self, command, channel, nickname, args):
"""Handle NPC events commands"""
# Normalize command
command = self.normalize_input(command)
if command == 'events':
await self.cmd_events(channel, nickname)
elif command == 'event':
await self.cmd_event(channel, nickname, args)
elif command == 'help' and len(args) > 0 and args[0].lower() == 'events':
await self.cmd_event_help(channel, nickname)
elif command == 'contribute':
await self.cmd_contribute(channel, nickname, args)
elif command == 'eventhelp':
await self.cmd_event_help(channel, nickname)
async def cmd_events(self, channel, nickname):
"""Show all active NPC events"""
try:
active_events = await self.events_manager.get_active_events()
if not active_events:
self.send_message(channel, "📅 No active community events at the moment. Check back later!")
return
message = "🎯 **Active Community Events:**\n"
for event in active_events:
progress = self.events_manager.get_progress_bar(
event['current_contributions'],
event['target_contributions']
)
# Calculate time remaining
expires_at = datetime.fromisoformat(event['expires_at'])
time_left = expires_at - datetime.now()
if time_left.total_seconds() > 0:
hours_left = int(time_left.total_seconds() / 3600)
minutes_left = int((time_left.total_seconds() % 3600) / 60)
time_str = f"{hours_left}h {minutes_left}m"
else:
time_str = "Expired"
difficulty_stars = "" * event['difficulty']
message += f"\n**#{event['id']} - {event['title']}** {difficulty_stars}\n"
message += f"📝 {event['description']}\n"
message += f"📊 Progress: {progress}\n"
message += f"⏰ Time left: {time_str}\n"
message += f"💰 Reward: {event['reward_experience']} XP, ${event['reward_money']}\n"
message += f"🤝 Use `!contribute {event['id']}` to help!\n"
self.send_message(channel, message)
except Exception as e:
print(f"Error in cmd_events: {e}")
self.send_message(channel, f"❌ Error fetching events: {str(e)}")
async def cmd_event(self, channel, nickname, args):
"""Show details for a specific event"""
if not args:
self.send_message(channel, "❌ Usage: !event <event_id>")
return
try:
event_id = int(args[0])
event = await self.events_manager.get_event_details(event_id)
if not event:
self.send_message(channel, f"❌ Event #{event_id} not found or expired.")
return
# Calculate time remaining
expires_at = datetime.fromisoformat(event['expires_at'])
time_left = expires_at - datetime.now()
if time_left.total_seconds() > 0:
hours_left = int(time_left.total_seconds() / 3600)
minutes_left = int((time_left.total_seconds() % 3600) / 60)
time_str = f"{hours_left}h {minutes_left}m"
else:
time_str = "Expired"
progress = self.events_manager.get_progress_bar(
event['current_contributions'],
event['target_contributions']
)
difficulty_stars = "" * event['difficulty']
status_emoji = "🔄" if event['status'] == 'active' else "" if event['status'] == 'completed' else ""
message = f"{status_emoji} **Event #{event['id']}: {event['title']}** {difficulty_stars}\n"
message += f"📝 {event['description']}\n"
message += f"📊 Progress: {progress}\n"
message += f"⏰ Time left: {time_str}\n"
message += f"💰 Reward: {event['reward_experience']} XP, ${event['reward_money']}\n"
message += f"🏆 Status: {event['status'].title()}\n"
# Show leaderboard if there are contributors
if event['leaderboard']:
message += "\n🏅 **Top Contributors:**\n"
for i, contributor in enumerate(event['leaderboard'][:5]): # Top 5
rank_emoji = ["🥇", "🥈", "🥉", "4", "5"][i]
message += f"{rank_emoji} {contributor['nickname']}: {contributor['contributions']} contributions\n"
if event['status'] == 'active':
message += f"\n🤝 Use `!contribute {event['id']}` to help!"
self.send_message(channel, message)
except ValueError:
self.send_message(channel, "❌ Invalid event ID. Please use a number.")
except Exception as e:
print(f"Error in cmd_event: {e}")
self.send_message(channel, f"❌ Error fetching event details: {str(e)}")
async def cmd_contribute(self, channel, nickname, args):
"""Allow player to contribute to an event"""
if not args:
self.send_message(channel, "❌ Usage: !contribute <event_id>")
return
player = await self.require_player(channel, nickname)
if not player:
return
try:
event_id = int(args[0])
# Check if event exists and is active
event = await self.events_manager.get_event_details(event_id)
if not event:
self.send_message(channel, f"❌ Event #{event_id} not found or expired.")
return
if event['status'] != 'active':
self.send_message(channel, f"❌ Event #{event_id} is not active.")
return
# Check if event has expired
expires_at = datetime.fromisoformat(event['expires_at'])
if datetime.now() >= expires_at:
self.send_message(channel, f"❌ Event #{event_id} has expired.")
return
# Add contribution
result = await self.events_manager.contribute_to_event(event_id, player['id'])
if not result['success']:
self.send_message(channel, f"❌ Failed to contribute: {result.get('error', 'Unknown error')}")
return
# Get updated event details
updated_event = await self.events_manager.get_event_details(event_id)
player_contributions = await self.events_manager.get_player_contributions(player['id'], event_id)
progress = self.events_manager.get_progress_bar(
updated_event['current_contributions'],
updated_event['target_contributions']
)
if result['event_completed']:
self.send_message(channel, f"🎉 **EVENT COMPLETED!** {updated_event['completion_message']}")
self.send_message(channel, f"🏆 Thanks to everyone who participated! Rewards will be distributed shortly.")
else:
self.send_message(channel, f"{nickname} contributed to '{updated_event['title']}'!")
self.send_message(channel, f"📊 Progress: {progress}")
self.send_message(channel, f"🤝 Your total contributions: {player_contributions}")
# Show encouragement based on progress
progress_percent = (updated_event['current_contributions'] / updated_event['target_contributions']) * 100
if progress_percent >= 75:
self.send_message(channel, "🔥 Almost there! Keep it up!")
elif progress_percent >= 50:
self.send_message(channel, "💪 Great progress! We're halfway there!")
elif progress_percent >= 25:
self.send_message(channel, "🌟 Good start! Keep contributing!")
except ValueError:
self.send_message(channel, "❌ Invalid event ID. Please use a number.")
except Exception as e:
print(f"Error in cmd_contribute: {e}")
self.send_message(channel, f"❌ Error contributing to event: {str(e)}")
async def cmd_event_help(self, channel, nickname):
"""Show help for NPC events system"""
message = """🎯 **Community Events System Help**
**Available Commands:**
`!events` - Show all active community events
`!event <id>` - Show details for a specific event
`!contribute <id>` - Contribute to an event
`!eventhelp` - Show this help message
**How Events Work:**
🌟 Random community events spawn regularly
🤝 All players can contribute to the same events
📊 Events have progress bars and time limits
🏆 Everyone who contributes gets rewards when completed
Events have different difficulty levels (1-3 stars)
**Event Types:**
🏪 Resource Gathering - Help collect supplies
🐾 Pet Rescue - Search for missing pets
🎪 Community Projects - Work together on town projects
🚨 Emergency Response - Help during crises
🔬 Research - Assist with scientific discoveries
**Tips:**
Check `!events` regularly for new opportunities
Higher difficulty events give better rewards
Contributing more increases your reward multiplier
Events expire after their time limit"""
self.send_message(channel, message)

View file

@ -57,6 +57,10 @@ class PetBotDebug:
loop.run_until_complete(self.validate_all_player_data())
print("✅ Player data validation complete")
print("🔄 Validating database integrity...")
loop.run_until_complete(self.validate_database_integrity())
print("✅ Database integrity validation complete")
print("🔄 Starting background validation task...")
self.start_background_validation(loop)
print("✅ Background validation started")
@ -142,6 +146,79 @@ class PetBotDebug:
print(f"❌ Error during player data validation: {e}")
# Don't fail startup if validation fails
async def validate_database_integrity(self):
"""Validate database integrity and fix common issues"""
try:
import aiosqlite
async with aiosqlite.connect(self.database.db_path) as db:
# Check for orphaned pets
cursor = await db.execute("""
SELECT COUNT(*) FROM pets
WHERE species_id NOT IN (SELECT id FROM pet_species)
""")
orphaned_pets = (await cursor.fetchone())[0]
if orphaned_pets > 0:
print(f"⚠️ Found {orphaned_pets} orphaned pets - fixing references...")
# This should not happen with the new startup logic, but just in case
await self.fix_orphaned_pets(db)
# Check player data accessibility
cursor = await db.execute("SELECT id, nickname FROM players")
players = await cursor.fetchall()
total_accessible_pets = 0
for player_id, nickname in players:
cursor = await db.execute("""
SELECT COUNT(*) FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ?
""", (player_id,))
accessible_pets = (await cursor.fetchone())[0]
total_accessible_pets += accessible_pets
if accessible_pets > 0:
print(f"{nickname}: {accessible_pets} pets accessible")
else:
# Get total pets for this player
cursor = await db.execute("SELECT COUNT(*) FROM pets WHERE player_id = ?", (player_id,))
total_pets = (await cursor.fetchone())[0]
if total_pets > 0:
print(f" ⚠️ {nickname}: {total_pets} pets but 0 accessible (orphaned)")
print(f"✅ Database integrity check: {total_accessible_pets} total accessible pets")
except Exception as e:
print(f"❌ Database integrity validation failed: {e}")
async def fix_orphaned_pets(self, db):
"""Fix orphaned pet references (emergency fallback)"""
try:
# This is a simplified fix - map common species names to current IDs
common_species = ['Flamey', 'Aqua', 'Leafy', 'Vinewrap', 'Bloomtail', 'Furry']
for species_name in common_species:
cursor = await db.execute("SELECT id FROM pet_species WHERE name = ?", (species_name,))
species_row = await cursor.fetchone()
if species_row:
current_id = species_row[0]
# Update any pets that might be referencing old IDs for this species
await db.execute("""
UPDATE pets SET species_id = ?
WHERE species_id NOT IN (SELECT id FROM pet_species)
AND species_id IN (
SELECT DISTINCT p.species_id FROM pets p
WHERE p.species_id NOT IN (SELECT id FROM pet_species)
LIMIT 1
)
""", (current_id,))
await db.commit()
print(" ✅ Orphaned pets fixed")
except Exception as e:
print(f" ❌ Failed to fix orphaned pets: {e}")
def start_background_validation(self, loop):
"""Start background task to periodically validate player data"""
import asyncio

View file

@ -19,7 +19,8 @@ from src.database import Database
from src.game_engine import GameEngine
from src.irc_connection_manager import IRCConnectionManager, ConnectionState
from src.rate_limiter import RateLimiter, get_command_category
from modules import CoreCommands, Exploration, BattleSystem, PetManagement, Achievements, Admin, Inventory, GymBattles, TeamBuilder
from src.npc_events import NPCEventsManager
from modules import CoreCommands, Exploration, BattleSystem, PetManagement, Achievements, Admin, Inventory, GymBattles, TeamBuilder, NPCEventsModule
from webserver import PetBotWebServer
from config import IRC_CONFIG, RATE_LIMIT_CONFIG
@ -42,6 +43,7 @@ class PetBotWithReconnect:
# Core components
self.database = Database()
self.game_engine = GameEngine(self.database)
self.npc_events = None
self.config = IRC_CONFIG
# Connection and state management
@ -82,6 +84,11 @@ class PetBotWithReconnect:
await self.game_engine.load_game_data()
self.logger.info("✅ Game data loaded")
# Initialize NPC events manager
self.logger.info("🔄 Initializing NPC events manager...")
self.npc_events = NPCEventsManager(self.database)
self.logger.info("✅ NPC events manager initialized")
# Load modules
self.logger.info("🔄 Loading command modules...")
await self.load_modules()
@ -118,6 +125,7 @@ class PetBotWithReconnect:
self.logger.info("🔄 Starting background tasks...")
asyncio.create_task(self.background_validation_task())
asyncio.create_task(self.connection_stats_task())
asyncio.create_task(self.npc_events.start_background_task())
self.logger.info("✅ Background tasks started")
self.logger.info("🎉 All components initialized successfully!")
@ -137,7 +145,8 @@ class PetBotWithReconnect:
Admin,
Inventory,
GymBattles,
TeamBuilder
TeamBuilder,
NPCEventsModule
]
self.modules = {}

View file

@ -36,10 +36,19 @@ class Database:
evolution_level INTEGER,
evolution_species_id INTEGER,
rarity INTEGER DEFAULT 1,
emoji TEXT,
FOREIGN KEY (evolution_species_id) REFERENCES pet_species (id)
)
""")
# Add emoji column if it doesn't exist (migration)
try:
await db.execute("ALTER TABLE pet_species ADD COLUMN emoji TEXT")
await db.commit()
except Exception:
# Column already exists or other error, which is fine
pass
await db.execute("""
CREATE TABLE IF NOT EXISTS pets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -160,6 +169,22 @@ class Database:
print(f"Migration warning: {e}")
pass # Don't fail if migration has issues
# Add fainted_at column for tracking when pets faint
try:
await db.execute("ALTER TABLE pets ADD COLUMN fainted_at TIMESTAMP DEFAULT NULL")
await db.commit()
print("Added fainted_at column to pets table")
except:
pass # Column already exists
# Add last_heal_time column for heal command cooldown
try:
await db.execute("ALTER TABLE players ADD COLUMN last_heal_time TIMESTAMP DEFAULT NULL")
await db.commit()
print("Added last_heal_time column to players table")
except:
pass # Column already exists
await db.execute("""
CREATE TABLE IF NOT EXISTS location_spawns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -340,6 +365,38 @@ class Database:
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS npc_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL,
difficulty INTEGER DEFAULT 1,
target_contributions INTEGER NOT NULL,
current_contributions INTEGER DEFAULT 0,
reward_experience INTEGER DEFAULT 0,
reward_money INTEGER DEFAULT 0,
reward_items TEXT,
status TEXT DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
completion_message TEXT
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS npc_event_contributions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER NOT NULL,
player_id INTEGER NOT NULL,
contributions INTEGER DEFAULT 0,
last_contribution_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (event_id) REFERENCES npc_events (id),
FOREIGN KEY (player_id) REFERENCES players (id),
UNIQUE(event_id, player_id)
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS verification_pins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -452,7 +509,7 @@ class Database:
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
query = """
SELECT p.*, ps.name as species_name, ps.type1, ps.type2
SELECT p.*, ps.name as species_name, ps.type1, ps.type2, ps.emoji
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ?
@ -854,7 +911,7 @@ class Database:
"""Add an item to player's inventory"""
async with aiosqlite.connect(self.db_path) as db:
# Get item ID
cursor = await db.execute("SELECT id FROM items WHERE name = ?", (item_name,))
cursor = await db.execute("SELECT id FROM items WHERE LOWER(name) = LOWER(?)", (item_name,))
item = await cursor.fetchone()
if not item:
return False
@ -909,7 +966,7 @@ class Database:
SELECT i.*, pi.quantity
FROM items i
JOIN player_inventory pi ON i.id = pi.item_id
WHERE pi.player_id = ? AND i.name = ?
WHERE pi.player_id = ? AND LOWER(i.name) = LOWER(?)
""", (player_id, item_name))
item = await cursor.fetchone()
@ -2056,4 +2113,336 @@ class Database:
return cursor.rowcount > 0
except Exception as e:
print(f"Error renaming team configuration: {e}")
return False
return False
# NPC Events System Methods
async def create_npc_event(self, event_data: Dict) -> int:
"""Create a new NPC event"""
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
INSERT INTO npc_events
(event_type, title, description, difficulty, target_contributions,
reward_experience, reward_money, reward_items, expires_at, completion_message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
event_data['event_type'],
event_data['title'],
event_data['description'],
event_data['difficulty'],
event_data['target_contributions'],
event_data['reward_experience'],
event_data['reward_money'],
event_data.get('reward_items', ''),
event_data['expires_at'],
event_data.get('completion_message', '')
))
await db.commit()
return cursor.lastrowid
async def get_active_npc_events(self) -> List[Dict]:
"""Get all active NPC events"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT * FROM npc_events
WHERE status = 'active' AND expires_at > datetime('now')
ORDER BY created_at DESC
""")
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def get_npc_event_by_id(self, event_id: int) -> Optional[Dict]:
"""Get a specific NPC event by ID"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT * FROM npc_events WHERE id = ?
""", (event_id,))
row = await cursor.fetchone()
return dict(row) if row else None
async def contribute_to_npc_event(self, event_id: int, player_id: int, contribution: int) -> Dict:
"""Add player contribution to an NPC event"""
async with aiosqlite.connect(self.db_path) as db:
try:
await db.execute("BEGIN TRANSACTION")
# Insert or update player contribution
await db.execute("""
INSERT OR REPLACE INTO npc_event_contributions
(event_id, player_id, contributions, last_contribution_at)
VALUES (?, ?,
COALESCE((SELECT contributions FROM npc_event_contributions
WHERE event_id = ? AND player_id = ?), 0) + ?,
CURRENT_TIMESTAMP)
""", (event_id, player_id, event_id, player_id, contribution))
# Update total contributions for the event
await db.execute("""
UPDATE npc_events
SET current_contributions = current_contributions + ?
WHERE id = ?
""", (contribution, event_id))
# Check if event is completed
cursor = await db.execute("""
SELECT current_contributions, target_contributions
FROM npc_events WHERE id = ?
""", (event_id,))
row = await cursor.fetchone()
if row and row[0] >= row[1]:
# Mark event as completed
await db.execute("""
UPDATE npc_events
SET status = 'completed'
WHERE id = ?
""", (event_id,))
await db.commit()
return {"success": True, "event_completed": True}
await db.commit()
return {"success": True, "event_completed": False}
except Exception as e:
await db.execute("ROLLBACK")
return {"success": False, "error": str(e)}
async def get_player_event_contributions(self, player_id: int, event_id: int) -> int:
"""Get player's contributions to a specific event"""
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT contributions FROM npc_event_contributions
WHERE event_id = ? AND player_id = ?
""", (event_id, player_id))
row = await cursor.fetchone()
return row[0] if row else 0
async def get_event_leaderboard(self, event_id: int) -> List[Dict]:
"""Get leaderboard for an event"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT p.nickname, c.contributions, c.last_contribution_at
FROM npc_event_contributions c
JOIN players p ON c.player_id = p.id
WHERE c.event_id = ?
ORDER BY c.contributions DESC
""", (event_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def expire_npc_events(self) -> int:
"""Mark expired events as expired and return count"""
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
UPDATE npc_events
SET status = 'expired'
WHERE status = 'active' AND expires_at <= datetime('now')
""")
await db.commit()
return cursor.rowcount
async def distribute_event_rewards(self, event_id: int) -> Dict:
"""Distribute rewards to all participants of a completed event"""
async with aiosqlite.connect(self.db_path) as db:
try:
await db.execute("BEGIN TRANSACTION")
# Get event details
cursor = await db.execute("""
SELECT reward_experience, reward_money, reward_items
FROM npc_events WHERE id = ? AND status = 'completed'
""", (event_id,))
event_row = await cursor.fetchone()
if not event_row:
await db.execute("ROLLBACK")
return {"success": False, "error": "Event not found or not completed"}
reward_exp, reward_money, reward_items = event_row
# Get all participants
cursor = await db.execute("""
SELECT player_id, contributions
FROM npc_event_contributions
WHERE event_id = ?
""", (event_id,))
participants = await cursor.fetchall()
# Distribute rewards
for player_id, contributions in participants:
# Scale rewards based on contribution (minimum 50% of full reward)
contribution_multiplier = max(0.5, min(1.0, contributions / 10))
final_exp = int(reward_exp * contribution_multiplier)
final_money = int(reward_money * contribution_multiplier)
# Update player rewards
await db.execute("""
UPDATE players
SET experience = experience + ?, money = money + ?
WHERE id = ?
""", (final_exp, final_money, player_id))
await db.commit()
return {"success": True, "participants_rewarded": len(participants)}
except Exception as e:
await db.execute("ROLLBACK")
return {"success": False, "error": str(e)}
# Pet Healing System Methods
async def get_fainted_pets(self, player_id: int) -> List[Dict]:
"""Get all fainted pets for a player"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT p.*, ps.name as species_name, ps.emoji
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ? AND p.fainted_at IS NOT NULL
ORDER BY p.fainted_at DESC
""", (player_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def revive_pet(self, pet_id: int, new_hp: int) -> bool:
"""Revive a fainted pet and restore HP"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
UPDATE pets
SET hp = ?, fainted_at = NULL
WHERE id = ?
""", (new_hp, pet_id))
await db.commit()
return True
except Exception as e:
print(f"Error reviving pet: {e}")
return False
async def faint_pet(self, pet_id: int) -> bool:
"""Mark a pet as fainted"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
UPDATE pets
SET hp = 0, fainted_at = CURRENT_TIMESTAMP
WHERE id = ?
""", (pet_id,))
await db.commit()
return True
except Exception as e:
print(f"Error fainting pet: {e}")
return False
async def get_pets_for_auto_recovery(self) -> List[Dict]:
"""Get pets that are eligible for auto-recovery (fainted for 30+ minutes)"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT p.*, ps.name as species_name
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.fainted_at IS NOT NULL
AND p.fainted_at <= datetime('now', '-30 minutes')
""")
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def auto_recover_pet(self, pet_id: int) -> bool:
"""Auto-recover a pet to 1 HP after 30 minutes"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
UPDATE pets
SET hp = 1, fainted_at = NULL
WHERE id = ?
""", (pet_id,))
await db.commit()
return True
except Exception as e:
print(f"Error auto-recovering pet: {e}")
return False
async def get_last_heal_time(self, player_id: int) -> Optional[datetime]:
"""Get the last time a player used the heal command"""
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT last_heal_time FROM players WHERE id = ?
""", (player_id,))
row = await cursor.fetchone()
if row and row[0]:
return datetime.fromisoformat(row[0])
return None
async def update_last_heal_time(self, player_id: int) -> bool:
"""Update the last heal time for a player"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
UPDATE players
SET last_heal_time = CURRENT_TIMESTAMP
WHERE id = ?
""", (player_id,))
await db.commit()
return True
except Exception as e:
print(f"Error updating last heal time: {e}")
return False
async def get_active_pets(self, player_id: int) -> List[Dict]:
"""Get all active pets for a player (excluding fainted pets)"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT p.*, ps.name as species_name, ps.emoji
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ? AND p.is_active = 1 AND p.fainted_at IS NULL
ORDER BY p.team_order
""", (player_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def get_player_pets(self, player_id: int, active_only: bool = False) -> List[Dict]:
"""Get all pets for a player, optionally filtering to active only"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
if active_only:
cursor = await db.execute("""
SELECT p.*, ps.name as species_name, ps.emoji
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ? AND p.is_active = 1 AND p.fainted_at IS NULL
ORDER BY p.team_order
""", (player_id,))
else:
cursor = await db.execute("""
SELECT p.*, ps.name as species_name, ps.emoji
FROM pets p
JOIN pet_species ps ON p.species_id = ps.id
WHERE p.player_id = ?
ORDER BY p.is_active DESC, p.team_order, p.level DESC
""", (player_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]

View file

@ -16,6 +16,7 @@ class GameEngine:
self.type_chart = {}
self.weather_patterns = {}
self.weather_task = None
self.pet_recovery_task = None
self.shutdown_event = asyncio.Event()
async def load_game_data(self):
@ -28,6 +29,7 @@ class GameEngine:
await self.database.initialize_gyms()
await self.init_weather_system()
await self.battle_engine.load_battle_data()
await self.start_pet_recovery_system()
async def load_pet_species(self):
try:
@ -35,19 +37,28 @@ class GameEngine:
species_data = json.load(f)
async with aiosqlite.connect(self.database.db_path) as db:
for species in species_data:
await db.execute("""
INSERT OR IGNORE INTO pet_species
(name, type1, type2, base_hp, base_attack, base_defense,
base_speed, evolution_level, rarity)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
species["name"], species["type1"], species.get("type2"),
species["base_hp"], species["base_attack"], species["base_defense"],
species["base_speed"], species.get("evolution_level"),
species.get("rarity", 1)
))
await db.commit()
# Check if species already exist to avoid re-inserting and changing IDs
cursor = await db.execute("SELECT COUNT(*) FROM pet_species")
existing_count = (await cursor.fetchone())[0]
if existing_count == 0:
# Only insert if no species exist to avoid ID conflicts
for species in species_data:
await db.execute("""
INSERT OR IGNORE INTO pet_species
(name, type1, type2, base_hp, base_attack, base_defense,
base_speed, evolution_level, rarity, emoji)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
species["name"], species["type1"], species.get("type2"),
species["base_hp"], species["base_attack"], species["base_defense"],
species["base_speed"], species.get("evolution_level"),
species.get("rarity", 1), species.get("emoji", "🐾")
))
await db.commit()
print(f"✅ Loaded {len(species_data)} pet species into database")
else:
print(f"✅ Found {existing_count} existing pet species - skipping reload to preserve IDs")
except FileNotFoundError:
await self.create_default_species()
@ -664,7 +675,90 @@ class GameEngine:
except Exception as e:
print(f"Error checking expired weather: {e}")
async def get_pet_emoji(self, species_name: str) -> str:
"""Get emoji for a pet species"""
try:
async with aiosqlite.connect(self.database.db_path) as db:
cursor = await db.execute(
"SELECT emoji FROM pet_species WHERE name = ?",
(species_name,)
)
row = await cursor.fetchone()
return row[0] if row and row[0] else "🐾"
except Exception:
return "🐾" # Default emoji if something goes wrong
def format_pet_name_with_emoji(self, pet_name: str, emoji: str = None, species_name: str = None) -> str:
"""Format pet name with emoji for display in IRC or web
Args:
pet_name: The pet's nickname or species name
emoji: Optional emoji to use (if None, will look up by species_name)
species_name: Species name for emoji lookup if emoji not provided
Returns:
Formatted string like "🔥 Flamey" or "🐉 Infernowyrm"
"""
if emoji:
return f"{emoji} {pet_name}"
elif species_name:
# This would need to be async, but for IRC we can use sync fallback
return f"🐾 {pet_name}" # Use default for now, can be enhanced later
else:
return f"🐾 {pet_name}"
async def start_pet_recovery_system(self):
"""Start the background pet recovery task"""
if self.pet_recovery_task is None or self.pet_recovery_task.done():
print("🏥 Starting pet recovery background task...")
self.pet_recovery_task = asyncio.create_task(self._pet_recovery_loop())
async def stop_pet_recovery_system(self):
"""Stop the background pet recovery task"""
print("🏥 Stopping pet recovery background task...")
if self.pet_recovery_task and not self.pet_recovery_task.done():
self.pet_recovery_task.cancel()
try:
await self.pet_recovery_task
except asyncio.CancelledError:
pass
async def _pet_recovery_loop(self):
"""Background task that checks for pets eligible for auto-recovery"""
try:
while not self.shutdown_event.is_set():
try:
# Check every 5 minutes for pets that need recovery
await asyncio.sleep(300) # 5 minutes
if self.shutdown_event.is_set():
break
# Get pets eligible for auto-recovery (fainted for 30+ minutes)
eligible_pets = await self.database.get_pets_for_auto_recovery()
if eligible_pets:
print(f"🏥 Auto-recovering {len(eligible_pets)} pet(s) after 30 minutes...")
for pet in eligible_pets:
success = await self.database.auto_recover_pet(pet["id"])
if success:
print(f" 🏥 Auto-recovered {pet['nickname'] or pet['species_name']} (ID: {pet['id']}) to 1 HP")
else:
print(f" ❌ Failed to auto-recover pet ID: {pet['id']}")
except asyncio.CancelledError:
break
except Exception as e:
print(f"Error in pet recovery loop: {e}")
# Continue the loop even if there's an error
await asyncio.sleep(60) # Wait a minute before retrying
except asyncio.CancelledError:
print("Pet recovery task cancelled")
async def shutdown(self):
"""Gracefully shutdown the game engine"""
print("🔄 Shutting down game engine...")
await self.stop_weather_system()
await self.stop_weather_system()
await self.stop_pet_recovery_system()

293
src/npc_events.py Normal file
View file

@ -0,0 +1,293 @@
"""
NPC Events System
Manages random collaborative events that all players can participate in
"""
import asyncio
import random
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from src.database import Database
class NPCEventsManager:
def __init__(self, database: Database):
self.database = database
self.active_events = {}
self.event_templates = {
1: [ # Difficulty 1 - Easy
{
"event_type": "resource_gathering",
"title": "Village Supply Run",
"description": "The village needs supplies! Help gather resources by exploring and finding items.",
"target_contributions": 25,
"reward_experience": 50,
"reward_money": 100,
"completion_message": "🎉 The village has enough supplies! Everyone who helped gets rewarded!",
"duration_hours": 4
},
{
"event_type": "pet_rescue",
"title": "Lost Pet Search",
"description": "A pet has gone missing! Help search different locations to find clues.",
"target_contributions": 20,
"reward_experience": 40,
"reward_money": 80,
"completion_message": "🐾 The lost pet has been found safe! Thanks to everyone who helped search!",
"duration_hours": 3
},
{
"event_type": "community_project",
"title": "Park Cleanup",
"description": "The local park needs cleaning! Help by contributing your time and effort.",
"target_contributions": 30,
"reward_experience": 35,
"reward_money": 75,
"completion_message": "🌳 The park is clean and beautiful again! Great teamwork everyone!",
"duration_hours": 5
}
],
2: [ # Difficulty 2 - Medium
{
"event_type": "emergency_response",
"title": "Storm Recovery",
"description": "A storm has damaged the town! Help with recovery efforts by contributing resources and time.",
"target_contributions": 50,
"reward_experience": 100,
"reward_money": 200,
"completion_message": "⛈️ The town has recovered from the storm! Everyone's hard work paid off!",
"duration_hours": 6
},
{
"event_type": "festival_preparation",
"title": "Annual Festival Setup",
"description": "The annual pet festival is coming! Help set up decorations and prepare activities.",
"target_contributions": 40,
"reward_experience": 80,
"reward_money": 150,
"completion_message": "🎪 The festival is ready! Thanks to everyone who helped prepare!",
"duration_hours": 8
},
{
"event_type": "research_expedition",
"title": "Scientific Discovery",
"description": "Researchers need help documenting rare pets! Contribute by exploring and reporting findings.",
"target_contributions": 35,
"reward_experience": 90,
"reward_money": 180,
"completion_message": "🔬 The research is complete! Your discoveries will help future generations!",
"duration_hours": 7
}
],
3: [ # Difficulty 3 - Hard
{
"event_type": "crisis_response",
"title": "Regional Emergency",
"description": "A regional crisis requires immediate community response! All trainers needed!",
"target_contributions": 75,
"reward_experience": 150,
"reward_money": 300,
"completion_message": "🚨 Crisis averted! The entire region is safe thanks to your heroic efforts!",
"duration_hours": 12
},
{
"event_type": "legendary_encounter",
"title": "Legendary Pet Sighting",
"description": "A legendary pet has been spotted! Help researchers track and document this rare encounter.",
"target_contributions": 60,
"reward_experience": 200,
"reward_money": 400,
"completion_message": "✨ The legendary pet has been successfully documented! History has been made!",
"duration_hours": 10
},
{
"event_type": "ancient_mystery",
"title": "Ancient Ruins Discovery",
"description": "Ancient ruins have been discovered! Help archaeologists uncover the secrets within.",
"target_contributions": 80,
"reward_experience": 180,
"reward_money": 350,
"completion_message": "🏛️ The ancient secrets have been revealed! Your efforts uncovered lost knowledge!",
"duration_hours": 14
}
]
}
async def start_background_task(self):
"""Start the background task that manages NPC events"""
while True:
try:
# Check for expired events
await self.expire_events()
# Distribute rewards for completed events
await self.distribute_completed_rewards()
# Maybe spawn a new event
await self.maybe_spawn_event()
# Wait 30 minutes before next check
await asyncio.sleep(30 * 60)
except Exception as e:
print(f"Error in NPC events background task: {e}")
await asyncio.sleep(5 * 60) # Wait 5 minutes on error
async def expire_events(self):
"""Mark expired events as expired"""
try:
expired_count = await self.database.expire_npc_events()
if expired_count > 0:
print(f"🕐 {expired_count} NPC events expired")
except Exception as e:
print(f"Error expiring NPC events: {e}")
async def distribute_completed_rewards(self):
"""Distribute rewards for completed events"""
try:
# Get completed events that haven't distributed rewards yet
completed_events = await self.database.get_active_npc_events()
for event in completed_events:
if event['status'] == 'completed':
result = await self.database.distribute_event_rewards(event['id'])
if result['success']:
print(f"🎁 Distributed rewards for event '{event['title']}' to {result['participants_rewarded']} players")
except Exception as e:
print(f"Error distributing event rewards: {e}")
async def maybe_spawn_event(self):
"""Maybe spawn a new event based on conditions"""
try:
# Check if we have any active events
active_events = await self.database.get_active_npc_events()
# Don't spawn if we already have 2 or more active events
if len(active_events) >= 2:
return
# 20% chance to spawn a new event each check (every 30 minutes)
if random.random() < 0.2:
await self.spawn_random_event()
except Exception as e:
print(f"Error in maybe_spawn_event: {e}")
async def spawn_random_event(self):
"""Spawn a random event based on difficulty"""
try:
# Choose difficulty (weighted towards easier events)
difficulty_weights = {1: 0.6, 2: 0.3, 3: 0.1}
difficulty = random.choices(list(difficulty_weights.keys()),
weights=list(difficulty_weights.values()))[0]
# Choose random event template
templates = self.event_templates[difficulty]
template = random.choice(templates)
# Create event data
event_data = {
'event_type': template['event_type'],
'title': template['title'],
'description': template['description'],
'difficulty': difficulty,
'target_contributions': template['target_contributions'],
'reward_experience': template['reward_experience'],
'reward_money': template['reward_money'],
'completion_message': template['completion_message'],
'expires_at': (datetime.now() + timedelta(hours=template['duration_hours'])).isoformat()
}
# Create the event
event_id = await self.database.create_npc_event(event_data)
print(f"🎯 New NPC event spawned: '{template['title']}' (ID: {event_id}, Difficulty: {difficulty})")
return event_id
except Exception as e:
print(f"Error spawning random event: {e}")
return None
async def get_active_events(self) -> List[Dict]:
"""Get all active events"""
return await self.database.get_active_npc_events()
async def contribute_to_event(self, event_id: int, player_id: int, contribution: int = 1) -> Dict:
"""Add a player's contribution to an event"""
return await self.database.contribute_to_npc_event(event_id, player_id, contribution)
async def get_event_details(self, event_id: int) -> Optional[Dict]:
"""Get detailed information about an event"""
event = await self.database.get_npc_event_by_id(event_id)
if not event:
return None
# Add leaderboard
leaderboard = await self.database.get_event_leaderboard(event_id)
event['leaderboard'] = leaderboard
return event
async def get_player_contributions(self, player_id: int, event_id: int) -> int:
"""Get player's contributions to a specific event"""
return await self.database.get_player_event_contributions(player_id, event_id)
async def force_spawn_event(self, difficulty: int = 1) -> Optional[int]:
"""Force spawn an event (admin command)"""
if difficulty not in self.event_templates:
return None
templates = self.event_templates[difficulty]
template = random.choice(templates)
event_data = {
'event_type': template['event_type'],
'title': template['title'],
'description': template['description'],
'difficulty': difficulty,
'target_contributions': template['target_contributions'],
'reward_experience': template['reward_experience'],
'reward_money': template['reward_money'],
'completion_message': template['completion_message'],
'expires_at': (datetime.now() + timedelta(hours=template['duration_hours'])).isoformat()
}
return await self.database.create_npc_event(event_data)
async def force_spawn_specific_event(self, event_type: str, difficulty: int = 1) -> Optional[int]:
"""Force spawn a specific event type (admin command)"""
if difficulty not in self.event_templates:
return None
# Find template matching the event type
templates = self.event_templates[difficulty]
template = None
for t in templates:
if t['event_type'] == event_type:
template = t
break
if not template:
return None
event_data = {
'event_type': template['event_type'],
'title': template['title'],
'description': template['description'],
'difficulty': difficulty,
'target_contributions': template['target_contributions'],
'reward_experience': template['reward_experience'],
'reward_money': template['reward_money'],
'completion_message': template['completion_message'],
'expires_at': (datetime.now() + timedelta(hours=template['duration_hours'])).isoformat()
}
return await self.database.create_npc_event(event_data)
def get_progress_bar(self, current: int, target: int, width: int = 20) -> str:
"""Generate a progress bar for event progress"""
filled = int((current / target) * width)
bar = "" * filled + "" * (width - filled)
percentage = min(100, int((current / target) * 100))
return f"[{bar}] {percentage}% ({current}/{target})"

View file

@ -11,7 +11,7 @@ class CommandCategory(Enum):
BASIC = "basic" # !help, !ping, !status
GAMEPLAY = "gameplay" # !explore, !catch, !battle
MANAGEMENT = "management" # !pets, !activate, !deactivate
ADMIN = "admin" # !backup, !reload, !reconnect
ADMIN = "admin" # !reload, !setweather, !spawnevent
WEB = "web" # Web interface requests
@ -387,7 +387,6 @@ COMMAND_CATEGORIES = {
"ping": CommandCategory.BASIC,
"status": CommandCategory.BASIC,
"uptime": CommandCategory.BASIC,
"connection_stats": CommandCategory.BASIC,
# Gameplay commands
"start": CommandCategory.GAMEPLAY,
@ -411,13 +410,7 @@ COMMAND_CATEGORIES = {
"nickname": CommandCategory.MANAGEMENT,
# Admin commands
"backup": CommandCategory.ADMIN,
"restore": CommandCategory.ADMIN,
"backups": CommandCategory.ADMIN,
"backup_stats": CommandCategory.ADMIN,
"backup_cleanup": CommandCategory.ADMIN,
"reload": CommandCategory.ADMIN,
"reconnect": CommandCategory.ADMIN,
}

View file

@ -1,7 +1,7 @@
#!/bin/bash
#
# PetBot Startup Script
# Complete one-command startup for PetBot with all dependencies
# Complete one-command startup for PetBot with all dependencies and validation
#
# Usage: ./start_petbot.sh
#
@ -10,6 +10,8 @@ set -e # Exit on any error
echo "🐾 Starting PetBot..."
echo "===================="
echo "Version: $(date '+%Y-%m-%d %H:%M:%S') - Enhanced with startup validation"
echo ""
# Get script directory (works even if called from elsewhere)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@ -29,23 +31,22 @@ fi
echo "🔄 Activating virtual environment..."
source venv/bin/activate
# Upgrade pip to latest version
echo "🔄 Ensuring pip is up to date..."
pip install --upgrade pip -q
# Check if requirements are installed
echo "🔄 Checking dependencies..."
if ! python -c "import aiosqlite, irc, dotenv" 2>/dev/null; then
echo "📦 Installing/updating dependencies..."
if ! python -c "import aiosqlite, irc" 2>/dev/null; then
echo "📦 Installing/updating dependencies from requirements.txt..."
# Create requirements.txt if it doesn't exist
# Verify requirements.txt exists
if [ ! -f "requirements.txt" ]; then
echo "📝 Creating requirements.txt..."
cat > requirements.txt << EOF
aiosqlite>=0.17.0
irc>=20.3.0
python-dotenv>=0.19.0
aiohttp>=3.8.0
EOF
echo "❌ requirements.txt not found!"
echo "🔧 Please run install_prerequisites.sh first"
exit 1
fi
pip install --upgrade pip
pip install -r requirements.txt
echo "✅ Dependencies installed"
else
@ -60,44 +61,143 @@ sys.path.append('.')
try:
from src.database import Database
from src.game_engine import GameEngine
from src.game_engine import GameEngine
from src.rate_limiter import RateLimiter
from src.irc_connection_manager import IRCConnectionManager
from config import ADMIN_USER, IRC_CONFIG, RATE_LIMIT_CONFIG
print('✅ Core modules verified')
print(f' Admin user: {ADMIN_USER}')
print(f' IRC Channel: {IRC_CONFIG[\"channel\"]}')
print(f' Rate limiting: {\"Enabled\" if RATE_LIMIT_CONFIG[\"enabled\"] else \"Disabled\"}')
except ImportError as e:
print(f'❌ Module import error: {e}')
print('💡 Try running: ./install_prerequisites.sh')
sys.exit(1)
"
# Create data directory if it doesn't exist
if [ ! -d "data" ]; then
echo "📁 Creating data directory..."
mkdir -p data
# Create required directories
echo "🔄 Creating required directories..."
mkdir -p data backups logs
# Check configuration files
echo "🔄 Verifying configuration files..."
config_files=("config/pets.json" "config/locations.json" "config/items.json" "config/achievements.json" "config/gyms.json")
missing_configs=()
for config_file in "${config_files[@]}"; do
if [ ! -f "$config_file" ]; then
missing_configs+=("$config_file")
fi
done
if [ ${#missing_configs[@]} -gt 0 ]; then
echo "❌ Missing configuration files:"
for missing in "${missing_configs[@]}"; do
echo " - $missing"
done
echo "💡 These files are required for proper bot operation"
exit 1
fi
# Create backups directory if it doesn't exist
if [ ! -d "backups" ]; then
echo "📁 Creating backups directory..."
mkdir -p backups
fi
echo "✅ All configuration files present"
# Check if database exists, if not mention first-time setup
if [ ! -f "data/petbot.db" ]; then
# Database pre-flight check
if [ -f "data/petbot.db" ]; then
echo "🔄 Validating existing database..."
# Quick database validation
python3 -c "
import sqlite3
import sys
try:
conn = sqlite3.connect('data/petbot.db')
cursor = conn.cursor()
# Check essential tables exist
tables = ['players', 'pets', 'pet_species', 'locations']
for table in tables:
cursor.execute(f'SELECT COUNT(*) FROM {table}')
count = cursor.fetchone()[0]
print(f' ✅ {table}: {count} records')
conn.close()
print('✅ Database validation passed')
except Exception as e:
print(f'❌ Database validation failed: {e}')
print('💡 Database may be corrupted - backup and recreate if needed')
sys.exit(1)
"
else
echo " First-time setup detected - database will be created automatically"
fi
# Display startup information
# Pre-startup system test
echo "🔄 Running pre-startup system test..."
python3 -c "
import sys
sys.path.append('.')
try:
# Test basic imports and initialization
from run_bot_debug import PetBotDebug
# Create a test bot instance to verify everything loads
print(' 🔧 Testing bot initialization...')
bot = PetBotDebug()
print(' ✅ Bot instance created successfully')
print('✅ Pre-startup test passed')
except Exception as e:
print(f'❌ Pre-startup test failed: {e}')
import traceback
traceback.print_exc()
sys.exit(1)
"
# Display comprehensive startup information
echo ""
echo "🚀 Launching PetBot with Auto-Reconnect..."
echo "🌐 Web interface will be available at: http://localhost:8080"
echo "💬 IRC: Connecting to irc.libera.chat #petz"
echo "📊 Features: Rate limiting, auto-reconnect, web interface, team builder"
echo "🚀 Launching PetBot with Enhanced Features..."
echo "============================================="
echo "🌐 Web interface: http://localhost:8080"
echo "📱 Public access: http://petz.rdx4.com/"
echo "💬 IRC Server: irc.libera.chat"
echo "📢 IRC Channel: #petz"
echo "👤 Admin User: $(python3 -c 'from config import ADMIN_USER; print(ADMIN_USER)')"
echo ""
echo "🎮 Features Available:"
echo " ✅ 33 Pet Species with Emojis"
echo " ✅ 6 Exploration Locations"
echo " ✅ Team Builder with PIN Verification"
echo " ✅ Achievement System"
echo " ✅ Gym Battles"
echo " ✅ Weather System"
echo " ✅ Rate Limiting & Anti-Abuse"
echo " ✅ Auto-Reconnection"
echo " ✅ Startup Data Validation"
echo " ✅ Background Monitoring"
echo ""
echo "🔧 Technical Details:"
echo " 📊 Database: SQLite with validation"
echo " 🌐 Webserver: Integrated with bot instance"
echo " 🛡️ Security: Rate limiting enabled"
echo " 🔄 Reliability: Auto-reconnect on failure"
echo " 📈 Monitoring: Background validation every 30min"
echo ""
echo "Press Ctrl+C to stop the bot"
echo "===================="
echo "============================================="
echo ""
# Launch the bot
exec python run_bot_with_reconnect.py
# Launch the appropriate bot based on what's available
if [ -f "run_bot_with_reconnect.py" ]; then
echo "🚀 Starting with auto-reconnect support..."
exec python run_bot_with_reconnect.py
elif [ -f "run_bot_debug.py" ]; then
echo "🚀 Starting in debug mode..."
exec python run_bot_debug.py
else
echo "❌ No bot startup file found!"
echo "💡 Expected: run_bot_with_reconnect.py or run_bot_debug.py"
exit 1
fi

File diff suppressed because it is too large Load diff