-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
382 lines (362 loc) · 15.4 KB
/
main.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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
import discord
import os
import requests
import json
import re
from discord.ext import commands
from dotenv import load_dotenv
import math
intents = discord.Intents.default() # All but the THREE privileged ones
intents.message_content = True # Subscribe to the Message Content intent
#bot = commands.Bot(command_prefix='-', intents=intents) # Pass the intents into your commands.Bot or discord.Client
load_dotenv()
d100s = []
d20s = []
d12s = []
d10s = []
d8s = []
d6s = []
d4s = []
d3s = []
d2s = []
def parse_roll(DiceSides):
DiceSides = int(DiceSides)
if DiceSides == 100:
global d100s
if len(d100s) > 0:
RemovedFirstRoll = d100s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d100s = DiceResult
RemovedFirstRoll = d100s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 20:
global d20s
if len(d20s) > 0:
RemovedFirstRoll = d20s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d20s = DiceResult
RemovedFirstRoll = d20s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 12:
global d12s
if len(d12s) > 0:
RemovedFirstRoll = d12s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d12s = DiceResult
RemovedFirstRoll = d12s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 10:
global d10s
if len(d10s) > 0:
RemovedFirstRoll = d10s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d10s = DiceResult
RemovedFirstRoll = d10s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 8:
global d8s
if len(d8s) > 0:
RemovedFirstRoll = d8s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d8s = DiceResult
RemovedFirstRoll = d8s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 6:
global d6s
if len(d6s) > 0:
RemovedFirstRoll = d6s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d6s = DiceResult
RemovedFirstRoll = d6s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 4:
global d4s
if len(d4s) > 0:
RemovedFirstRoll = d4s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d4s = DiceResult
RemovedFirstRoll = d4s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 3:
global d3s
if len(d3s) > 0:
RemovedFirstRoll = d3s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d3s = DiceResult
RemovedFirstRoll = d3s.pop(0)
return(RemovedFirstRoll)
if DiceSides == 2:
global d2s
if len(d2s) > 0:
RemovedFirstRoll = d2s.pop(0)
return(RemovedFirstRoll)
else:
DiceResult = roll_100_dice(DiceSides)
d2s = DiceResult
RemovedFirstRoll = d2s.pop(0)
return(RemovedFirstRoll)
else: #DiceSides isn't allowed
DiceResult = "only 100, 20, 12, 10, 8, 6, 4, 3, and 2 sided dice are allowed"
return()
def parse_factor(passFactor):
if "d" in passFactor: #is a dice expression to evaluate
DiceNum, DiceSides = passFactor.split('d', 1)
if int(DiceSides) in [100, 20, 12, 10, 8, 6, 4, 3, 2]: #check that supported sided dice are being asked for
FactorX = []
for x in range(0, int(DiceNum)):
FactorX.append(parse_roll(DiceSides))
return(FactorX) #if is a dice expression, this rolls the resulting numbers as a list
else:
ErrorMsg = 99999 #add a numeric code impossible to be rolled that can be teted for and passed as int
return(ErrorMsg)
else: #is a modifier
ConvertedModifier = [int(passFactor)]
return(list(ConvertedModifier)) #returns modifier as a list
def roll_1d100(): #get one roll from the list of up to 100 in the variable d100s and delete the first one after using it
d100 = roll_d100() #get the whole list of 100 rolls
global d100s
RemovedFirstRoll = d100s.pop(0) #remove first value in global d100s variable
return(RemovedFirstRoll)
bot = commands.Bot(command_prefix=commands.when_mentioned_or("?"), description="A dice bot using random.org", intents=intents)
def roll_100_dice(DiceSides):
IscusHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain', 'User-Agent': 'Iscus Discord Bot, joshsays@gmail.com'}
APIkey = os.getenv('KEY')
response = requests.post('https://api.random.org/json-rpc/2/invoke', json={'jsonrpc':'2.0','method':'generateIntegers','params':{'apiKey':APIkey,'n':100,'min':1,'max':DiceSides,'replacement':True,'base':10},'id':42},headers=IscusHeaders)
json_data = json.loads(response.text)
if DiceSides == 100:
global d100s
d100s = list(json_data['result']['random']['data'])
return(d100s)
if DiceSides == 20:
global d20s
d20s = list(json_data['result']['random']['data'])
return(d20s)
if DiceSides == 12:
global d12s
d12s = list(json_data['result']['random']['data'])
return(d12s)
if DiceSides == 10:
global d10s
d10s = list(json_data['result']['random']['data'])
return(d10s)
if DiceSides == 8:
global d8s
d8s = list(json_data['result']['random']['data'])
return(d8s)
if DiceSides == 6:
global d6s
d6s = list(json_data['result']['random']['data'])
return(d6s)
if DiceSides == 4:
global d4s
d4s = list(json_data['result']['random']['data'])
return(d4s)
if DiceSides == 3:
global d3s
d3s = list(json_data['result']['random']['data'])
return(d3s)
if DiceSides == 2:
global d2s
d2s = list(json_data['result']['random']['data'])
return(d2s)
else:
return()
#function to use the random.org Sequence API to pick one integer from FactorCount
def PickSequence(FactorCount):
IscusHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain', 'User-Agent': 'Iscus Discord Bot, joshsays@gmail.com'}
APIkey = os.getenv('KEY')
response = requests.post('https://api.random.org/json-rpc/2/invoke', json={'jsonrpc':'2.0','method':'generateIntegerSequences','params':{'apiKey':APIkey,'n':1,'length':1,'min':1,'max':FactorCount,'replacement':False,'base':10},'id':42},headers=IscusHeaders)
json_data = json.loads(response.text)
ChosenOne = str(json_data['result']['random']['data'])
return(ChosenOne)
@bot.event
async def on_ready():
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="you ?roll your fate..."))
print('{0.user} now weaves your fate...'.format(bot))
#bot help command
@bot.command(name='about', help='Responds with info about using the bot')
async def about(ctx):
helpfile = """
__**Iscus, The Lady of Our Fate, is a dice bot for Discord**__
*All rolls are drawn from RANDOM.ORG*
*See: https://www.random.org/*
**How to use:**
> Type a message beginning with ?roll followed by a dice expression. Example: ?roll 1d100
> A maximum of four combinations of dice and/or modifiers are permitted per roll, in any order, such as ?roll 2d8+2-1d4+1, or ?roll 1d100+1d20+2d10-4d6. Each factor must be separated by a plus (+) or minus (-) symbol.
> You can add a comment to a roll by adding a space after the roll expression and typing whatever you want, like: ?roll 1d100 Job Trident(85)
> Type ?help about or just ?about to see this info later
**New Features in v3.0:**
> Made the bot compatible with the latest version of discord.py and Discord's new API
> Added the ?pick command to allow the GM to randomly pick from a list of comma separated names
**Known issues:**
> Not all error handling cases discovered yet; I'm sure your fat fingers will point out bugs before long...
> Only these dice types are supported: d100,d20, d12, d10, d8, d6, d4, d3, and d2. You can't roll a d7 for example.
May Chael ever watch over your rolls and Phyth never weave them for you... Enjoy!
"""
await ctx.send(helpfile)
#bot roll command
@bot.command(name='roll', help='Responds with a roll result based on the dice or modifier formula input like: ?roll 2d8+1+1d4-1d3')
async def roll(ctx, RollPattern):
#store each roll factor in a List and test how many factors there are in the RollPattern, a factor being a number of dice like 3d6 or a modifier separated by a + or -
RollFactors = re.split('(\\+|\\-)', RollPattern)
FactorCount = int(len(RollFactors))
#check that there aren't more than the maximum 7 factors (4 rolls or modifiers)
if 'd' not in RollFactors[0]:
ErrorMsg = "Your dice expression must begin with a dice roll in the format 1d100, 2d8, 3d6, etc."
await ctx.send(ErrorMsg)
if FactorCount > 7:
ErrorMsg = "Sorry, you may only roll a maximum of 4 dice or modifiers in any combination like: ?roll 1d8+1+1d4+2-1d3"
await ctx.send(ErrorMsg)
else: #the roll expression is valid, parse it
if FactorCount == 1: #there is only one roll factor e.g. 1d100
DiceNum, DiceSides = RollFactors[0].split('d', 1)
#check that supported sided dice are being asked for
if int(DiceSides) in [100, 20, 12, 10, 8, 6, 4, 3, 2]:
Factor1 = []
for x in range(0, int(DiceNum)):
Factor1.append(parse_roll(DiceSides))
sumFactor1 = sum(Factor1)
await ctx.send("{} rolls {} = {}".format(ctx.message.author.display_name, Factor1, sumFactor1))
return
else:
ErrorMsg = "Only d100, d20, d12, d10, d8, d6, d4, d3, d2 are supported."
await ctx.send(ErrorMsg)
return
if FactorCount == 3: #there are two factors joined by a + or - like 1d8+1 or 1d6+1d4
passFactor = RollFactors[0]
Factor1 = parse_factor(passFactor)
passFactor = RollFactors[2]
Factor2 = parse_factor(passFactor)
Operator1 = RollFactors[1]
if Factor1 == 99999 or Factor2 == 99999:
ErrorMsg = "Only d100, d20, d12, d10, d8, d6, d4, d3, d2 are supported."
await ctx.send(ErrorMsg)
return
elif "+" in Operator1:
RollSum = sum(Factor1 + Factor2)
else:
RollSum = int(sum(Factor1)) - int(sum(Factor2))
await ctx.send("{} rolls {} {} {} = {}".format(ctx.message.author.display_name, Factor1, Operator1, Factor2, RollSum))
return
if FactorCount == 5: #there are three factors joined by a + or - like 1d8+1d4-3
passFactor = RollFactors[0]
Factor1 = parse_factor(passFactor)
passFactor = RollFactors[2]
Factor2 = parse_factor(passFactor)
passFactor = RollFactors[4]
Factor3 = parse_factor(passFactor)
Operator1 = RollFactors[1]
Operator2 = RollFactors[3]
if Factor1 == 99999 or Factor2 == 99999 or Factor3 == 99999:
ErrorMsg = "Only d100, d20, d12, d10, d8, d6, d4, d3, d2 are supported."
await ctx.send(ErrorMsg)
return
elif "+" in Operator1:
F1F2Sum = sum(Factor1 + Factor2)
else:
F1F2Sum = int(sum(Factor1)) - int(sum(Factor2))
if "+" in Operator2:
RollSum = F1F2Sum + int(sum(Factor3))
else:
RollSum = int(F1F2Sum) - int(sum(Factor3))
await ctx.send("{} rolls {} {} {} {} {} = {}".format(ctx.message.author.display_name, Factor1, Operator1, Factor2, Operator2, Factor3, RollSum))
return
if FactorCount == 7: #there are four factors joined by a + or - like 3d8-1d4+13-1d3
passFactor = RollFactors[0]
Factor1 = parse_factor(passFactor)
passFactor = RollFactors[2]
Factor2 = parse_factor(passFactor)
passFactor = RollFactors[4]
Factor3 = parse_factor(passFactor)
passFactor = RollFactors[6]
Factor4 = parse_factor(passFactor)
Operator1 = RollFactors[1]
Operator2 = RollFactors[3]
Operator3 = RollFactors[5]
if Factor1 == 99999 or Factor2 == 99999 or Factor3 == 99999 or Factor4 == 99999:
ErrorMsg = "Only d100, d20, d12, d10, d8, d6, d4, d3, d2 are supported."
await ctx.send(ErrorMsg)
return
elif "+" in Operator1:
F1F2Sum = sum(Factor1 + Factor2)
else:
F1F2Sum = int(sum(Factor1)) - int(sum(Factor2))
if "+" in Operator2:
F1F2F3Sum = F1F2Sum + int(sum(Factor3))
else:
F1F2F3Sum = int(F1F2Sum) - int(sum(Factor3))
if "+" in Operator3:
RollSum = F1F2F3Sum + int(sum(Factor4))
else:
RollSum = int(F1F2F3Sum) - int(sum(Factor4))
await ctx.send("{} rolls {} {} {} {} {} {} {} = {}".format(ctx.message.author.display_name, Factor1, Operator1, Factor2, Operator2, Factor3, Operator3, Factor4, RollSum))
return
else:
ErrorMsg = "Oops, I think you typed something wrong: your roll should end in a dice or a modifier, not a + or -"
await ctx.send(ErrorMsg)
#debug functions reply to ping
@bot.command(name='ping', help='Check to see if the bot is listening')
async def ping(ctx):
messageReply = "I await your actions to reveal your fate..."
await ctx.send(messageReply)
#bot skill command
@bot.command(name='skill', help='accepts a skill % as a number and responds with the degree of success or failure based on a 1d100 roll.')
async def skill(ctx, Skill):
#ensure that an integer was entered after the word skill
try:
int(Skill)
except ValueError:
messageReply = "To roll vs. your skill you must enter your skill % as a number like ?skill 51"
await ctx.send(messageReply)
finally: #roll 1d100 vs the skill
DiceSides = 100
Skill = int(Skill)
Roll = int(parse_roll(DiceSides))
if Roll <= int(math.ceil(Skill/20)) or Roll == 1: #was a Critical rolled rounding up?
emoji = '<:crit:872962421097123931>'
await ctx.send("{} rolls a {}, Critical! {}".format(ctx.message.author.display_name, Roll, emoji))
elif Roll >= (101-(100-Skill)/20) or Roll==100: #was a Fumble rolled rounding down?
emoji = ':football:'
await ctx.send("{} rolls a {}, Fumble {}".format(ctx.message.author.display_name, Roll, emoji))
elif Roll <= int(math.ceil(Skill/5)): #was a Special rolled rounding up?
emoji = '<:special:872964395330863144>'
await ctx.send("{} rolls a {}, Special! {}".format(ctx.message.author.display_name, Roll, emoji))
elif Roll > Skill or Roll >=96: #was a Failure rolled?
emoji = ':thumbsdown:'
await ctx.send("{} rolls a {}, Failure {}".format(ctx.message.author.display_name, Roll, emoji))
elif Roll <= Skill: #was a Success rolled?
emoji = ':thumbsup:'
await ctx.send("{} rolls a {}, Success! {}".format(ctx.message.author.display_name, Roll, emoji))
else:
messageReply = "Oops, something went wrong"
await ctx.send(messageReply)
#bot pick command
@bot.command(name='pick', help='accepts any number of names separated by commas and randomly selects one of them. Usually used by the GM to pick a victim... Example: ?pick Job,Brian,Dom,Ulrik')
async def pick(ctx, Pick):
#store each name (string) in a List and test how many factors there are in the PickFactors, a factor being a string separated by a blank space
PickFactors = re.split(',', Pick)
FactorCount = int(len(PickFactors))
PickListPosition = PickSequence(FactorCount)
#line below removes brackets from the string value so it can be converted to int
PickListPosition = re.sub(r"[\[\]]",'',PickListPosition)
PickListPosition = int(PickListPosition) - 1
#line below converts the random value to start with 0 to correspond to the PickFactors list order and pops that value out of the string
LuckyVictim = PickFactors[PickListPosition]
await ctx.send("{} has been chosen by Fate...".format(LuckyVictim))
bot.run(os.getenv('TOKEN'))