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>
This commit is contained in:
megaproxy 2025-07-16 11:34:01 +00:00
parent 530134bd36
commit cd2ad10aec
2 changed files with 529 additions and 0 deletions

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})"