Compare commits
8 commits
add7731d80
...
cd2ad10aec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd2ad10aec | ||
|
|
530134bd36 | ||
|
|
8ae7da8379 | ||
|
|
adcd5afd85 | ||
|
|
a333306ad3 | ||
|
|
d758d6b924 | ||
|
|
72c1098a22 | ||
|
|
fca0423c84 |
18 changed files with 3076 additions and 147 deletions
|
|
@ -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": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
315
config/pets.json
315
config/pets.json
|
|
@ -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
366
help.html
|
|
@ -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 <pet> <new_nickname></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 <item name></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 <id></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 <id></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 <location|all> <weather_type> [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>
|
||||
|
|
@ -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'
|
||||
]
|
||||
194
modules/admin.py
194
modules/admin.py
|
|
@ -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)}")
|
||||
|
|
@ -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!\"")
|
||||
|
|
|
|||
|
|
@ -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
236
modules/npc_events.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
397
src/database.py
397
src/database.py
|
|
@ -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]
|
||||
|
|
@ -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
293
src/npc_events.py
Normal 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})"
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
164
start_petbot.sh
164
start_petbot.sh
|
|
@ -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
|
||||
900
webserver.py
900
webserver.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue