Skip to content

Commit ec24588

Browse files
committed
Implement combat mechanics and duel system
- Added combat mechanics including hit chance, damage calculation, and special attacks in `combat_mechanics.py`. - Developed views for duel requests, combat actions, and equipment management in `combat_views.py`. - Created tests for battle simulation, combat mechanics, and duel command functionality. - Established mock data for testing user stats, weapons, and items in `test_duel_fixtures.py`. - Ensured proper interaction handling and error management in views and commands.
1 parent 4baef5a commit ec24588

File tree

13 files changed

+2227
-45
lines changed

13 files changed

+2227
-45
lines changed

bot/commands/duel_test.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
from discord import app_commands, Interaction, Member
2+
from discord.ext import commands
3+
from discord.ui import View, Button, Select
4+
from typing import Dict, Optional, List
5+
from datetime import datetime
6+
import discord
7+
from bot.utils.combat_mechanics_test import (
8+
calculate_hit_chance, calculate_damage,
9+
calculate_special_attack, calculate_combat_stats,
10+
apply_prayer_bonuses
11+
)
12+
from bot.utils.combat_views_test import DuelRequestView, CombatActionView, EquipmentView
13+
from bot.utils.DButil_v2 import (
14+
get_user_stats, get_user_equipment,
15+
get_user_inventory, update_user_stats,
16+
update_duel_history
17+
)
18+
19+
class DuelState:
20+
"""State management for an active duel"""
21+
def __init__(
22+
self,
23+
challenger: Member,
24+
opponent: Member,
25+
challenger_stats: Dict,
26+
opponent_stats: Dict,
27+
challenger_equipment: Dict,
28+
opponent_equipment: Dict,
29+
start_time: datetime
30+
):
31+
self.challenger = challenger
32+
self.opponent = opponent
33+
self.challenger_stats = challenger_stats
34+
self.opponent_stats = opponent_stats
35+
self.challenger_equipment = challenger_equipment
36+
self.opponent_equipment = opponent_equipment
37+
self.start_time = start_time
38+
39+
self.current_turn = challenger
40+
self.winner = None
41+
self.special_attack_cooldown = {}
42+
43+
def get_opponent(self, user: Member) -> Member:
44+
"""Get opponent of given user"""
45+
return self.opponent if user.id == self.challenger.id else self.challenger
46+
47+
def get_health(self, user: Member) -> int:
48+
"""Get current health of user"""
49+
stats = self.challenger_stats if user.id == self.challenger.id else self.opponent_stats
50+
return stats["current_health"]
51+
52+
def get_equipment(self, user: Member) -> Dict:
53+
"""Get equipment of user"""
54+
return self.challenger_equipment if user.id == self.challenger.id else self.opponent_equipment
55+
56+
def get_stats(self, user: Member) -> Dict:
57+
"""Get stats of user"""
58+
return self.challenger_stats if user.id == self.challenger.id else self.opponent_stats
59+
60+
def create_status_embed(self) -> discord.Embed:
61+
"""Create status embed for current duel state"""
62+
embed = discord.Embed(title="Duel Status", color=0x00ff00)
63+
64+
# Add challenger info
65+
challenger_health = self.get_health(self.challenger)
66+
challenger_weapon = self.challenger_equipment.get("weapon", {}).get("name", "Unarmed")
67+
embed.add_field(
68+
name=f"{self.challenger.display_name}",
69+
value=f"Health: {challenger_health}\nWeapon: {challenger_weapon}",
70+
inline=True
71+
)
72+
73+
# Add opponent info
74+
opponent_health = self.get_health(self.opponent)
75+
opponent_weapon = self.opponent_equipment.get("weapon", {}).get("name", "Unarmed")
76+
embed.add_field(
77+
name=f"{self.opponent.display_name}",
78+
value=f"Health: {opponent_health}\nWeapon: {opponent_weapon}",
79+
inline=True
80+
)
81+
82+
# Add current turn indicator
83+
embed.add_field(
84+
name="Current Turn",
85+
value=f"{self.current_turn.display_name}'s turn!",
86+
inline=False
87+
)
88+
89+
return embed
90+
91+
class DuelCommand(commands.Cog):
92+
"""Duel command implementation"""
93+
def __init__(self, bot):
94+
self.bot = bot
95+
self.active_duels: Dict[int, DuelState] = {}
96+
self.DUEL_TIMEOUT = 300 # 5 minutes
97+
98+
@app_commands.command(name="duel")
99+
async def duel(self, interaction: Interaction, opponent: Member):
100+
"""Challenge another user to a duel"""
101+
102+
# Validate users
103+
if opponent.id == interaction.user.id:
104+
await interaction.response.send_message(
105+
"You can't duel yourself!",
106+
ephemeral=True
107+
)
108+
return
109+
110+
if opponent.bot:
111+
await interaction.response.send_message(
112+
"You can't duel a bot!",
113+
ephemeral=True
114+
)
115+
return
116+
117+
# Check if either user is in an active duel
118+
if interaction.user.id in self.active_duels:
119+
await interaction.response.send_message(
120+
"You're already in a duel!",
121+
ephemeral=True
122+
)
123+
return
124+
125+
if opponent.id in self.active_duels:
126+
await interaction.response.send_message(
127+
f"{opponent.display_name} is already in a duel!",
128+
ephemeral=True
129+
)
130+
return
131+
132+
# Create duel request view
133+
view = DuelRequestView(
134+
challenger=interaction.user,
135+
opponent=opponent,
136+
timeout=60
137+
)
138+
139+
# Send duel request
140+
await interaction.response.send_message(
141+
f"{interaction.user.mention} has challenged {opponent.mention} to a duel!",
142+
view=view
143+
)
144+
145+
# Wait for response
146+
await view.wait()
147+
148+
if view.accepted:
149+
# Initialize duel state
150+
duel_state = await self._initialize_duel(interaction.user, opponent)
151+
self.active_duels[interaction.user.id] = duel_state
152+
self.active_duels[opponent.id] = duel_state
153+
154+
# Start combat
155+
await self._start_combat(interaction, duel_state)
156+
157+
elif view.declined:
158+
await interaction.followup.send(
159+
f"{opponent.display_name} declined the duel!"
160+
)
161+
162+
else:
163+
await interaction.followup.send(
164+
"The duel request timed out."
165+
)
166+
167+
async def _initialize_duel(
168+
self,
169+
challenger: Member,
170+
opponent: Member
171+
) -> DuelState:
172+
"""Initialize duel state with user stats and equipment"""
173+
challenger_stats = await get_user_stats(challenger.id)
174+
opponent_stats = await get_user_stats(opponent.id)
175+
176+
challenger_equipment = await get_user_equipment(challenger.id)
177+
opponent_equipment = await get_user_equipment(opponent.id)
178+
179+
return DuelState(
180+
challenger=challenger,
181+
opponent=opponent,
182+
challenger_stats=challenger_stats,
183+
opponent_stats=opponent_stats,
184+
challenger_equipment=challenger_equipment,
185+
opponent_equipment=opponent_equipment,
186+
start_time=datetime.now()
187+
)
188+
189+
async def _start_combat(
190+
self,
191+
interaction: Interaction,
192+
duel_state: DuelState
193+
):
194+
"""Start combat phase of duel"""
195+
embed = duel_state.create_status_embed()
196+
view = CombatActionView(
197+
attacker=duel_state.current_turn,
198+
defender=duel_state.get_opponent(duel_state.current_turn),
199+
duel_state=duel_state
200+
)
201+
202+
await interaction.followup.send(
203+
"Battle begins!",
204+
embed=embed,
205+
view=view
206+
)
207+
208+
async def _handle_duel_completion(self, user_id: int):
209+
"""Handle duel completion and cleanup"""
210+
if user_id in self.active_duels:
211+
duel_state = self.active_duels[user_id]
212+
213+
# Update user stats
214+
await update_user_stats(
215+
duel_state.winner.id,
216+
{"total_wins": "+1"}
217+
)
218+
await update_user_stats(
219+
duel_state.get_opponent(duel_state.winner).id,
220+
{"total_losses": "+1"}
221+
)
222+
223+
# Record duel history
224+
await update_duel_history(
225+
winner_id=duel_state.winner.id,
226+
loser_id=duel_state.get_opponent(duel_state.winner).id,
227+
duration=(datetime.now() - duel_state.start_time).seconds,
228+
winner_hp=duel_state.get_health(duel_state.winner),
229+
loser_hp=0
230+
)
231+
232+
# Cleanup
233+
challenger_id = duel_state.challenger.id
234+
opponent_id = duel_state.opponent.id
235+
self.active_duels.pop(challenger_id, None)
236+
self.active_duels.pop(opponent_id, None)
237+
238+
async def setup(bot: commands.Bot):
239+
await bot.add_cog(DuelCommand(bot))

0 commit comments

Comments
 (0)