-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbot.py
278 lines (234 loc) · 12.1 KB
/
bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import discord
import random
import os
from discord.ext import commands
from discord.ui import View, Select
from dotenv import load_dotenv
# Cargar variables de entorno
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
# Configurar intents y el bot
intents = discord.Intents.default()
intents.message_content = True # Habilita la lectura de mensajes
intents.dm_messages = True # Permite enviar y recibir mensajes privados
bot = commands.Bot(command_prefix='!', intents=intents)
character_creation_sessions = {}
# Opciones para la creación de personajes
RACES = {
"Humano": [],
"Elfo": ["Elfo Nocturno", "Elfo del Bosque", "Alto Elfo"],
"Enano": ["Enano de las Montañas", "Enano de las Colinas"],
"Orco": [],
"Mediano": ["Mediano Furtivo", "Mediano Robusto"],
"Tiefling": []
}
CLASSES = ["Guerrero", "Mago", "Pícaro", "Clérigo", "Paladín", "Explorador"]
BACKGROUNDS = ["Soldado", "Noble", "Forajido", "Aldeano", "Erudito", "Marinero"]
STATS = ["Fuerza", "Destreza", "Constitución", "Inteligencia", "Sabiduría", "Carisma"]
EMOJI_STATS = {"Fuerza": "🦾 Fuerza", "Destreza": "💨 Destreza", "Constitución": "❤ Constitución", "Inteligencia": "🧠 Inteligencia", "Sabiduría": "🤓 Sabiduría", "Carisma": "✨ Carisma"}
def roll_stats():
return sorted([sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]) for _ in range(6)], reverse=True)
@bot.event
async def on_ready():
print(f'Bot conectado como {bot.user}')
print(character_creation_sessions)
@bot.event
async def on_message(message):
if message.author.bot:
return
await bot.process_commands(message)
# Comando para lanzar dados
@bot.command(name='roll', help='Lanza un dado. Ejemplo: !roll 3d6')
async def roll(ctx, dice: str):
try:
results = []
modifier = 0
rolls, sides = map(str, dice.lower().split('d'))
if '+' in sides:
sides,modifier = map(int,sides.split('+'))
elif '-' in sides:
sides,modifier = map(int,sides.split('-'))
modifier *= -1
results = [random.randint(1, int(sides)) for _ in range(int(rolls))]
await ctx.send(f'🎲 {ctx.author.mention} lanzó {dice}: **{results}** = **{sum(results) + modifier}**')
except ValueError:
await ctx.send('Formato incorrecto. Usa algo como "2d6" o "1d20".')
@bot.command(name='check', help='Lanza un d20 con ventaja, desventaja o normal.')
async def check(ctx, type: str = None, bonus: int = 0):
try:
message = ''
result = [random.randint(1, 20) for _ in range(2)]
if type is None or (type.lstrip('-').isdigit()):
if type and (type.lstrip('-').isdigit()):
bonus = int(type)
roll = random.randint(1, 20)
message = f'🎲 {ctx.author.mention} Check normal: **{roll}** Total: {roll + bonus})'
elif type.lower() == "adv":
message = f'🎲 {ctx.author.mention} Check con ventaja: **{result}** → **{max(result)}**'
elif type.lower() == "dis":
message = f'🎲 {ctx.author.mention} Check con desventaja: **{result}** → **{min(result)}**'
else:
message = f'⚠️ {ctx.author.mention} Parámetro no válido.\n' \
' - **adv** para ventaja.\n' \
' - **dis** para desventaja.\n' \
' - Deja el comando sin argumento para un check normal.'
await ctx.send(message)
except ValueError:
await ctx.send('❌ Error: formato incorrecto. Usa algo como "!check adv" o "!check dis".')
# Comando para agregar nuevas funcionalidades en el futuro
@bot.command(name='add_command', help='Comando de prueba para futuras expansiones')
async def add_command(ctx):
await ctx.send('Este comando es un placeholder para futuras funcionalidades.')
@bot.command(name='create', help='Inicia la creación de un personaje de D&D 5e')
async def create_character(ctx):
user_id = ctx.author.id
print(f"Ejecutando create_character para {ctx.author}")
if user_id in character_creation_sessions:
await ctx.author.send("Ya tienes una sesión de creación de personaje en curso. Usa `!cancel_character` si deseas reiniciar.")
return
try:
dm_channel = await ctx.author.create_dm()
await dm_channel.send("¡Vamos a crear tu personaje de D&D 5e! Primero, dime el **nombre** de tu personaje:")
bot.loop.create_task(capture_name(user_id, ctx.author))
print("Mensaje enviado con éxito")
except discord.Forbidden:
print("⚠️ Error: No se pudo enviar el mensaje privado, el usuario tiene DMs desactivados")
await ctx.send(f"{ctx.author.mention}, no puedo enviarte un mensaje privado. Activa los DMs en tu configuración.")
character_creation_sessions[user_id] = {"nombre": None, "raza": None, "clase": None, "trasfondo": None, "stats": None}
@bot.command(name='cancel', help='Cancela la creación del personaje en curso')
async def cancel_character(ctx):
user_id = ctx.author.id
if user_id in character_creation_sessions:
del character_creation_sessions[user_id]
await ctx.author.send("❌ Creación de personaje cancelada.")
else:
await ctx.author.send("No tienes ninguna sesión de creación en curso.")
async def capture_name(user_id, user):
def check(m):
return m.author.id == user_id and isinstance(m.channel, discord.DMChannel)
try:
message = await bot.wait_for("message", check=check, timeout=120)
character_creation_sessions[user_id]["nombre"] = message.content
await user.send("Ahora elige tu **raza o subraza**:", view=create_race_menu(user_id))
except TimeoutError:
await user.send("⏳ Tiempo de espera agotado. Usa `!create` para comenzar de nuevo.")
del character_creation_sessions[user_id]
def create_race_menu(user_id):
view = View()
options = []
for race, subraces in RACES.items():
if subraces:
options.append(discord.SelectOption(label=race, value=f"header_{race}", description="Selecciona una subraza", default=False))
for subrace in subraces:
options.append(discord.SelectOption(label=f" ├─ {subrace}", value=subrace))
else:
options.append(discord.SelectOption(label=race, value=race))
select = Select(
placeholder="Elige tu raza o subraza",
options=options,
)
async def select_callback(interaction):
selected_value = select.values[0]
if selected_value.startswith("header_"):
await interaction.response.defer() # No hacer nada si se elige un encabezado
return
for race, subraces in RACES.items():
if selected_value in subraces:
character_creation_sessions[user_id]["raza"] = race
character_creation_sessions[user_id]["subraza"] = selected_value
break
else:
if selected_value in RACES and RACES[selected_value]:
await interaction.response.send_message("Debes seleccionar una subraza para esta raza.", ephemeral=True)
return
character_creation_sessions[user_id]["raza"] = selected_value
character_creation_sessions[user_id]["subraza"] = None
await interaction.response.edit_message(content=f'Raza elegida: **{character_creation_sessions[user_id]["raza"]}** {"(" + character_creation_sessions[user_id]["subraza"] + ")" if character_creation_sessions[user_id]["subraza"] else ""}', view=None)
await interaction.user.send("Ahora elige tu **clase**:", view=create_class_menu(user_id))
select.callback = select_callback
view.add_item(select)
return view
def create_class_menu(user_id):
view = View()
select = Select(
placeholder="Elige tu clase",
options=[discord.SelectOption(label=cl) for cl in CLASSES],
)
async def select_callback(interaction):
character_creation_sessions[user_id]["clase"] = select.values[0]
await interaction.response.edit_message(content=f'Clase elegida: **{character_creation_sessions[user_id]["clase"]}**', view=None)
await interaction.user.send("Ahora elige tu **trasfondo**:", view=create_background_menu(user_id))
select.callback = select_callback
view.add_item(select)
return view
def create_background_menu(user_id):
view = View()
select = Select(
placeholder="Elige tu trasfondo",
options=[discord.SelectOption(label=bg) for bg in BACKGROUNDS],
)
async def select_callback(interaction):
character_creation_sessions[user_id]["trasfondo"] = select.values[0]
await interaction.response.edit_message(content=f'Trasfondo elegido: **{character_creation_sessions[user_id]["trasfondo"]}**', view=None)
await assign_stats(interaction, user_id)
select.callback = select_callback
view.add_item(select)
return view
async def assign_stats(interaction, user_id):
character_creation_sessions[user_id]["stats"] = {}
rolled_values = roll_stats()
await interaction.user.send(f"Has tirado los siguientes valores de estadísticas: {', '.join(map(str, rolled_values))}")
async def assign_next_stat():
if not rolled_values:
await apply_race_bonuses(interaction, user_id)
return
stat_index = len(character_creation_sessions[user_id]["stats"])
if stat_index >= len(STATS):
return
stat = STATS[stat_index]
valid_values = list(set(rolled_values)) # Asegura que no haya valores repetidos en el desplegable
view = View()
select = Select(
placeholder=f"Asigna un valor a {stat}",
options=[discord.SelectOption(label=str(value), value=str(value)) for value in valid_values],
)
async def select_callback(stat_interaction):
selected_value = int(stat_interaction.data['values'][0])
character_creation_sessions[user_id]["stats"][stat] = selected_value
rolled_values.remove(selected_value) # Remueve solo una instancia del valor seleccionado
await stat_interaction.response.defer()
await assign_next_stat()
select.callback = select_callback
view.add_item(select)
await interaction.user.send(f"Selecciona el valor para {stat}:", view=view)
await assign_next_stat()
async def apply_race_bonuses(interaction, user_id):
char = character_creation_sessions[user_id]
race = char["raza"]
race_bonuses = {
"Humano": {"Fuerza": 1, "Destreza": 1, "Constitución": 1, "Inteligencia": 1, "Sabiduría": 1, "Carisma": 1},
"Elfo": {"Destreza": 2},
"Enano": {"Constitución": 2},
"Orco": {"Fuerza": 2, "Constitución": 1},
"Mediano": {"Destreza": 2},
"Tiefling": {"Carisma": 2, "Inteligencia": 1}
}
if race in race_bonuses:
for stat, bonus in race_bonuses[race].items():
stat_with_emoji = EMOJI_STATS[stat] # Ajusta el nombre para coincidir con las claves de `char["stats"]`
if stat_with_emoji in char["stats"]:
char["stats"][stat_with_emoji] += bonus
char["stats"] = {EMOJI_STATS.get(stat, stat): value for stat, value in char["stats"].items()}
await finalize_character(interaction, user_id)
async def finalize_character(interaction, user_id):
char = character_creation_sessions[user_id]
stats_summary = "\n".join([f"{k}: {v}" for k, v in char["stats"].items()])
character_summary = f"""🎭 **Nuevo Personaje Creado** 🎭\n\n**Nombre:** {char["nombre"]}\n**Raza:** {char["raza"]}\n**Clase:** {char["clase"]}\n**Trasfondo:** {char["trasfondo"]}\n\n**Estadísticas:**\n{stats_summary}"""
await interaction.user.send("✅ ¡Personaje creado con éxito! Será publicado en el canal principal.")
main_channel = discord.utils.get(bot.get_all_channels(), name="📜personajes")
if main_channel:
await main_channel.send(character_summary)
else:
await interaction.user.send("No encontré el canal para publicar el personaje. Contacta al administrador.")
del character_creation_sessions[user_id]
bot.run(TOKEN)