✨ PEP in utils
This commit is contained in:
11
gwendolyn/funcs/__init__.py
Normal file
11
gwendolyn/funcs/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""A collection of all Gwendolyn functions."""
|
||||
|
||||
__all__ = ["Games", "Money", "LookupFuncs", "StarWars"]
|
||||
|
||||
from .games import Money, Games
|
||||
|
||||
from .lookup import LookupFuncs
|
||||
|
||||
from .other import Other
|
||||
|
||||
from .star_wars_funcs import StarWars
|
6
gwendolyn/funcs/games/__init__.py
Normal file
6
gwendolyn/funcs/games/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""Functions for games Gwendolyn can play."""
|
||||
|
||||
__all__ = ["Money", "Games"]
|
||||
|
||||
from .money import Money
|
||||
from .games_container import Games
|
1469
gwendolyn/funcs/games/blackjack.py
Normal file
1469
gwendolyn/funcs/games/blackjack.py
Normal file
File diff suppressed because it is too large
Load Diff
1068
gwendolyn/funcs/games/connect_four.py
Normal file
1068
gwendolyn/funcs/games/connect_four.py
Normal file
File diff suppressed because it is too large
Load Diff
48
gwendolyn/funcs/games/games_container.py
Normal file
48
gwendolyn/funcs/games/games_container.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""
|
||||
Has a container for game functions.
|
||||
|
||||
*Classes*
|
||||
---------
|
||||
Games
|
||||
Container for game functions.
|
||||
"""
|
||||
|
||||
|
||||
from .invest import Invest
|
||||
from .trivia import Trivia
|
||||
from .blackjack import Blackjack
|
||||
from .connect_four import ConnectFour
|
||||
from .hangman import Hangman
|
||||
from .hex import HexGame
|
||||
|
||||
|
||||
class Games():
|
||||
"""
|
||||
Contains game classes.
|
||||
|
||||
*Attributes*
|
||||
------------
|
||||
bot: Gwendolyn
|
||||
The instance of Gwendolyn.
|
||||
invest
|
||||
Contains investment functions.
|
||||
blackjack
|
||||
Contains blackjack functions.
|
||||
connect_four
|
||||
Contains connect four functions.
|
||||
hangman
|
||||
Contains hangman functions.
|
||||
hex
|
||||
Contains hex functions
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""Initialize the container."""
|
||||
self.bot = bot
|
||||
|
||||
self.invest = Invest(bot)
|
||||
self.trivia = Trivia(bot)
|
||||
self.blackjack = Blackjack(bot)
|
||||
self.connect_four = ConnectFour(bot)
|
||||
self.hangman = Hangman(bot)
|
||||
self.hex = HexGame(bot)
|
612
gwendolyn/funcs/games/hangman.py
Normal file
612
gwendolyn/funcs/games/hangman.py
Normal file
@ -0,0 +1,612 @@
|
||||
"""
|
||||
Deals with commands and logic for hangman games.
|
||||
|
||||
*Classes*
|
||||
---------
|
||||
Hangman()
|
||||
Deals with the game logic of hangman.
|
||||
DrawHangman()
|
||||
Draws the image shown to the player.
|
||||
"""
|
||||
import requests # Used for getting the word in Hangman.start()
|
||||
import datetime # Used for generating the game id
|
||||
import string # string.ascii_uppercase used
|
||||
import discord # Used for discord.file and type hints
|
||||
import math # Used by DrawHangman(), mainly for drawing circles
|
||||
import random # Used to draw poorly
|
||||
|
||||
from discord_slash.context import SlashContext # Used for typehints
|
||||
from PIL import ImageDraw, Image, ImageFont # Used to draw the image
|
||||
|
||||
|
||||
class Hangman():
|
||||
"""
|
||||
Controls hangman commands and game logic.
|
||||
|
||||
*Methods*
|
||||
---------
|
||||
start(ctx: SlashContext)
|
||||
stop(ctx: SlashContext)
|
||||
guess(message: discord.message, user: str, guess: str)
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""
|
||||
Initialize the class.
|
||||
|
||||
*Attributes*
|
||||
------------
|
||||
draw: DrawHangman
|
||||
The DrawHangman used to draw the hangman image.
|
||||
APIURL: str
|
||||
The url to get the words from.
|
||||
APIPARAMS: dict
|
||||
The parameters to pass to every api call.
|
||||
"""
|
||||
self.__bot = bot
|
||||
self.__draw = DrawHangman(bot)
|
||||
self.__APIURL = "https://api.wordnik.com/v4/words.json/randomWords?"
|
||||
apiKey = self.__bot.credentials["wordnik_key"]
|
||||
self.__APIPARAMS = {
|
||||
"hasDictionaryDef": True,
|
||||
"minCorpusCount": 5000,
|
||||
"maxCorpusCount": -1,
|
||||
"minDictionaryCount": 1,
|
||||
"maxDictionaryCount": -1,
|
||||
"minLength": 3,
|
||||
"maxLength": 11,
|
||||
"limit": 1,
|
||||
"api_key": apiKey
|
||||
}
|
||||
|
||||
async def start(self, ctx: SlashContext):
|
||||
"""
|
||||
Start a game of hangman.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: SlashContext
|
||||
The context of the command.
|
||||
"""
|
||||
await self.__bot.defer(ctx)
|
||||
channel = str(ctx.channel_id)
|
||||
user = f"#{ctx.author.id}"
|
||||
game = self.__bot.database["hangman games"].find_one({"_id": channel})
|
||||
user_name = self.__bot.database_funcs.get_name(user)
|
||||
startedGame = False
|
||||
|
||||
if game is None:
|
||||
word = "-"
|
||||
while "-" in word or "." in word:
|
||||
response = requests.get(self.__APIURL, params=self.__APIPARAMS)
|
||||
word = list(response.json()[0]["word"].upper())
|
||||
|
||||
self.__bot.log("Found the word \""+"".join(word)+"\"")
|
||||
guessed = [False] * len(word)
|
||||
gameID = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
newGame = {
|
||||
"_id": channel,
|
||||
"player": user,
|
||||
"guessed letters": [],
|
||||
"word": word,
|
||||
"game ID": gameID,
|
||||
"misses": 0,
|
||||
"guessed": guessed
|
||||
}
|
||||
self.__bot.database["hangman games"].insert_one(newGame)
|
||||
|
||||
remainingLetters = list(string.ascii_uppercase)
|
||||
|
||||
self.__draw.drawImage(channel)
|
||||
|
||||
log_message = "Game started"
|
||||
sendMessage = f"{user_name} started game of hangman."
|
||||
startedGame = True
|
||||
else:
|
||||
log_message = "There was already a game going on"
|
||||
sendMessage = self.__bot.long_strings["Hangman going on"]
|
||||
|
||||
self.__bot.log(log_message)
|
||||
await ctx.send(sendMessage)
|
||||
|
||||
if startedGame:
|
||||
boardsPath = "gwendolyn/resources/games/hangman_boards/"
|
||||
file_path = f"{boardsPath}hangman_board{channel}.png"
|
||||
newImage = await ctx.channel.send(file=discord.File(file_path))
|
||||
|
||||
blankMessage = await ctx.channel.send("_ _")
|
||||
reactionMessages = {
|
||||
newImage: remainingLetters[:15],
|
||||
blankMessage: remainingLetters[15:]
|
||||
}
|
||||
|
||||
old_messages = f"{newImage.id}\n{blankMessage.id}"
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "w") as f:
|
||||
f.write(old_messages)
|
||||
|
||||
for message, letters in reactionMessages.items():
|
||||
for letter in letters:
|
||||
emoji = chr(ord(letter)+127397)
|
||||
await message.add_reaction(emoji)
|
||||
|
||||
async def stop(self, ctx: SlashContext):
|
||||
"""
|
||||
Stop the game of hangman.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: SlashContext
|
||||
The context of the command.
|
||||
"""
|
||||
channel = str(ctx.channel.id)
|
||||
game = self.__bot.database["hangman games"].find_one({"_id": channel})
|
||||
|
||||
if game is None:
|
||||
await ctx.send("There's no game going on")
|
||||
elif f"#{ctx.author.id}" != game["player"]:
|
||||
await ctx.send("You can't end a game you're not in")
|
||||
else:
|
||||
self.__bot.database["hangman games"].delete_one({"_id": channel})
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f:
|
||||
messages = f.read().splitlines()
|
||||
|
||||
for message in messages:
|
||||
old_message = await ctx.channel.fetch_message(int(message))
|
||||
self.__bot.log("Deleting old message")
|
||||
await old_message.delete()
|
||||
|
||||
await ctx.send("Game stopped")
|
||||
|
||||
async def guess(self, message: discord.Message, user: str, guess: str):
|
||||
"""
|
||||
Guess a letter.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
message: discord.Message
|
||||
The message that the reaction was placed on.
|
||||
user: str
|
||||
The id of the user.
|
||||
guess: str
|
||||
The guess.
|
||||
"""
|
||||
channel = str(message.channel.id)
|
||||
hangman_games = self.__bot.database["hangman games"]
|
||||
game = hangman_games.find_one({"_id": channel})
|
||||
|
||||
gameExists = (game is not None)
|
||||
singleLetter = (len(guess) == 1 and guess.isalpha())
|
||||
newGuess = (guess not in game["guessed letters"])
|
||||
validGuess = (gameExists and singleLetter and newGuess)
|
||||
|
||||
if validGuess:
|
||||
self.__bot.log("Guessed the letter")
|
||||
correctGuess = 0
|
||||
|
||||
for x, letter in enumerate(game["word"]):
|
||||
if guess == letter:
|
||||
correctGuess += 1
|
||||
updater = {"$set": {f"guessed.{x}": True}}
|
||||
hangman_games.update_one({"_id": channel}, updater)
|
||||
|
||||
if correctGuess == 0:
|
||||
updater = {"$inc": {"misses": 1}}
|
||||
hangman_games.update_one({"_id": channel}, updater)
|
||||
|
||||
updater = {"$push": {"guessed letters": guess}}
|
||||
hangman_games.update_one({"_id": channel}, updater)
|
||||
|
||||
remainingLetters = list(string.ascii_uppercase)
|
||||
|
||||
game = hangman_games.find_one({"_id": channel})
|
||||
|
||||
for letter in game["guessed letters"]:
|
||||
remainingLetters.remove(letter)
|
||||
|
||||
if correctGuess == 1:
|
||||
sendMessage = "Guessed {}. There was 1 {} in the word."
|
||||
sendMessage = sendMessage.format(guess, guess)
|
||||
else:
|
||||
sendMessage = "Guessed {}. There were {} {}s in the word."
|
||||
sendMessage = sendMessage.format(guess, correctGuess, guess)
|
||||
|
||||
self.__draw.drawImage(channel)
|
||||
|
||||
if game["misses"] == 6:
|
||||
hangman_games.delete_one({"_id": channel})
|
||||
sendMessage += self.__bot.long_strings["Hangman lost game"]
|
||||
remainingLetters = []
|
||||
elif all(game["guessed"]):
|
||||
hangman_games.delete_one({"_id": channel})
|
||||
self.__bot.money.addMoney(user, 15)
|
||||
sendMessage += self.__bot.long_strings["Hangman guessed word"]
|
||||
remainingLetters = []
|
||||
|
||||
await message.channel.send(sendMessage)
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hangman{channel}", "r") as f:
|
||||
old_message_ids = f.read().splitlines()
|
||||
|
||||
for oldID in old_message_ids:
|
||||
old_message = await message.channel.fetch_message(int(oldID))
|
||||
self.__bot.log("Deleting old message")
|
||||
await old_message.delete()
|
||||
|
||||
boardsPath = "gwendolyn/resources/games/hangman_boards/"
|
||||
file_path = f"{boardsPath}hangman_board{channel}.png"
|
||||
newImage = await message.channel.send(file=discord.File(file_path))
|
||||
|
||||
if len(remainingLetters) > 0:
|
||||
if len(remainingLetters) > 15:
|
||||
blankMessage = await message.channel.send("_ _")
|
||||
reactionMessages = {
|
||||
newImage: remainingLetters[:15],
|
||||
blankMessage: remainingLetters[15:]
|
||||
}
|
||||
else:
|
||||
blankMessage = ""
|
||||
reactionMessages = {newImage: remainingLetters}
|
||||
|
||||
if blankMessage != "":
|
||||
old_messages = f"{newImage.id}\n{blankMessage.id}"
|
||||
else:
|
||||
old_messages = str(newImage.id)
|
||||
|
||||
old_imagePath = f"gwendolyn/resources/games/old_images/hangman{channel}"
|
||||
with open(old_imagePath, "w") as f:
|
||||
f.write(old_messages)
|
||||
|
||||
for message, letters in reactionMessages.items():
|
||||
for letter in letters:
|
||||
emoji = chr(ord(letter)+127397)
|
||||
await message.add_reaction(emoji)
|
||||
|
||||
|
||||
class DrawHangman():
|
||||
"""
|
||||
Draws the image of the hangman game.
|
||||
|
||||
*Methods*
|
||||
---------
|
||||
drawImage(channel: str)
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""
|
||||
Initialize the class.
|
||||
|
||||
*Attributes*
|
||||
------------
|
||||
CIRCLESIZE
|
||||
LINEWIDTH
|
||||
BODYSIZE
|
||||
LIMBSIZE
|
||||
ARMPOSITION
|
||||
MANX, MANY
|
||||
LETTERLINELENGTH
|
||||
LETTERLINEDISTANCE
|
||||
GALLOWX, GALLOWY
|
||||
PHI
|
||||
FONT
|
||||
SMALLFONT
|
||||
"""
|
||||
self.__bot = bot
|
||||
self.__CIRCLESIZE = 120
|
||||
self.__LINEWIDTH = 12
|
||||
|
||||
self.__BODYSIZE = 210
|
||||
self.__LIMBSIZE = 60
|
||||
self.__ARMPOSITION = 60
|
||||
|
||||
self.__MANX = (self.__LIMBSIZE*2)
|
||||
self.__MANY = (self.__CIRCLESIZE+self.__BODYSIZE+self.__LIMBSIZE)
|
||||
MANPADDING = self.__LINEWIDTH*4
|
||||
self.__MANX += MANPADDING
|
||||
self.__MANY += MANPADDING
|
||||
|
||||
self.__LETTERLINELENGTH = 90
|
||||
self.__LETTERLINEDISTANCE = 30
|
||||
|
||||
self.__GALLOWX, self.__GALLOWY = 360, 600
|
||||
self.__PHI = 1-(1 / ((1 + 5 ** 0.5) / 2))
|
||||
|
||||
LETTERSIZE = 75 # Wrong guesses letter size
|
||||
WORDSIZE = 70 # Correct guesses letter size
|
||||
|
||||
FONTPATH = "gwendolyn/resources/fonts/comic-sans-bold.ttf"
|
||||
self.__FONT = ImageFont.truetype(FONTPATH, LETTERSIZE)
|
||||
self.__SMALLFONT = ImageFont.truetype(FONTPATH, WORDSIZE)
|
||||
|
||||
def __deviate(self, preDeviance: int, preDevianceAccuracy: int,
|
||||
positionChange: float, maxmin: int,
|
||||
maxAcceleration: float):
|
||||
randomDeviance = random.uniform(-positionChange, positionChange)
|
||||
devianceAccuracy = preDevianceAccuracy + randomDeviance
|
||||
if devianceAccuracy > maxmin * maxAcceleration:
|
||||
devianceAccuracy = maxmin * maxAcceleration
|
||||
elif devianceAccuracy < -maxmin * maxAcceleration:
|
||||
devianceAccuracy = -maxmin * maxAcceleration
|
||||
|
||||
deviance = preDeviance + devianceAccuracy
|
||||
if deviance > maxmin:
|
||||
deviance = maxmin
|
||||
elif deviance < -maxmin:
|
||||
deviance = -maxmin
|
||||
return deviance, devianceAccuracy
|
||||
|
||||
def __badCircle(self):
|
||||
circlePadding = (self.__LINEWIDTH*3)
|
||||
imageWidth = self.__CIRCLESIZE+circlePadding
|
||||
imageSize = (imageWidth, imageWidth)
|
||||
background = Image.new("RGBA", imageSize, color=(0, 0, 0, 0))
|
||||
|
||||
d = ImageDraw.Draw(background, "RGBA")
|
||||
middle = (self.__CIRCLESIZE+(self.__LINEWIDTH*3))/2
|
||||
devianceX = 0
|
||||
devianceY = 0
|
||||
devianceAccuracyX = 0
|
||||
devianceAccuracyY = 0
|
||||
start = random.randint(-100, -80)
|
||||
degreesAmount = 360 + random.randint(-10, 30)
|
||||
|
||||
for degree in range(degreesAmount):
|
||||
devianceXParams = [
|
||||
devianceX,
|
||||
devianceAccuracyX,
|
||||
self.__LINEWIDTH/100,
|
||||
self.__LINEWIDTH,
|
||||
0.03
|
||||
]
|
||||
devianceYParams = [
|
||||
devianceY,
|
||||
devianceAccuracyY,
|
||||
self.__LINEWIDTH/100,
|
||||
self.__LINEWIDTH,
|
||||
0.03
|
||||
]
|
||||
devianceX, devianceAccuracyX = self.__deviate(*devianceXParams)
|
||||
devianceY, devianceAccuracyY = self.__deviate(*devianceYParams)
|
||||
|
||||
radians = math.radians(degree+start)
|
||||
circleX = (math.cos(radians) * (self.__CIRCLESIZE/2))
|
||||
circleY = (math.sin(radians) * (self.__CIRCLESIZE/2))
|
||||
|
||||
x = middle + circleX - (self.__LINEWIDTH/2) + devianceX
|
||||
y = middle + circleY - (self.__LINEWIDTH/2) + devianceY
|
||||
|
||||
circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)]
|
||||
d.ellipse(circlePosition, fill=(0, 0, 0, 255))
|
||||
|
||||
return background
|
||||
|
||||
def __badLine(self, length: int, rotated: bool = False):
|
||||
if rotated:
|
||||
w, h = length+self.__LINEWIDTH*3, self.__LINEWIDTH*3
|
||||
else:
|
||||
w, h = self.__LINEWIDTH*3, length+self.__LINEWIDTH*3
|
||||
background = Image.new("RGBA", (w, h), color=(0, 0, 0, 0))
|
||||
|
||||
d = ImageDraw.Draw(background, "RGBA")
|
||||
|
||||
possibleDeviance = int(self.__LINEWIDTH/3)
|
||||
devianceX = random.randint(-possibleDeviance, possibleDeviance)
|
||||
devianceY = 0
|
||||
devianceAccuracyX = 0
|
||||
devianceAccuracyY = 0
|
||||
|
||||
for pixel in range(length):
|
||||
devianceParamsX = [
|
||||
devianceX,
|
||||
devianceAccuracyX,
|
||||
self.__LINEWIDTH/1000,
|
||||
self.__LINEWIDTH,
|
||||
0.004
|
||||
]
|
||||
devianceParamsY = [
|
||||
devianceY,
|
||||
devianceAccuracyY,
|
||||
self.__LINEWIDTH/1000,
|
||||
self.__LINEWIDTH,
|
||||
0.004
|
||||
]
|
||||
devianceX, devianceAccuracyX = self.__deviate(*devianceParamsX)
|
||||
devianceY, devianceAccuracyY = self.__deviate(*devianceParamsY)
|
||||
|
||||
if rotated:
|
||||
x = self.__LINEWIDTH + pixel + devianceX
|
||||
y = self.__LINEWIDTH + devianceY
|
||||
else:
|
||||
x = self.__LINEWIDTH + devianceX
|
||||
y = self.__LINEWIDTH + pixel + devianceY
|
||||
|
||||
circlePosition = [(x, y), (x+self.__LINEWIDTH, y+self.__LINEWIDTH)]
|
||||
d.ellipse(circlePosition, fill=(0, 0, 0, 255))
|
||||
|
||||
return background
|
||||
|
||||
def __drawMan(self, misses: int, seed: str):
|
||||
random.seed(seed)
|
||||
manSize = (self.__MANX, self.__MANY)
|
||||
background = Image.new("RGBA", manSize, color=(0, 0, 0, 0))
|
||||
|
||||
if misses >= 1:
|
||||
head = self.__badCircle()
|
||||
pasteX = (self.__MANX-(self.__CIRCLESIZE+(self.__LINEWIDTH*3)))//2
|
||||
pastePosition = (pasteX, 0)
|
||||
background.paste(head, pastePosition, head)
|
||||
if misses >= 2:
|
||||
body = self.__badLine(self.__BODYSIZE)
|
||||
pasteX = (self.__MANX-(self.__LINEWIDTH*3))//2
|
||||
pastePosition = (pasteX, self.__CIRCLESIZE)
|
||||
background.paste(body, pastePosition, body)
|
||||
|
||||
if misses >= 3:
|
||||
limbs = random.sample(["rl", "ll", "ra", "la"], min(misses-2, 4))
|
||||
else:
|
||||
limbs = []
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
for limb in limbs:
|
||||
limbDrawing = self.__badLine(self.__LIMBSIZE, True)
|
||||
xPosition = (self.__MANX-(self.__LINEWIDTH*3))//2
|
||||
|
||||
if limb[1] == "a":
|
||||
rotation = random.randint(-45, 45)
|
||||
shift = math.sin(math.radians(rotation))
|
||||
lineLength = self.__LIMBSIZE+(self.__LINEWIDTH*3)
|
||||
compensation = int(shift*lineLength)
|
||||
limbDrawing = limbDrawing.rotate(rotation, expand=1)
|
||||
yPosition = self.__CIRCLESIZE + self.__ARMPOSITION
|
||||
if limb == "ra":
|
||||
compensation = min(-compensation, 0)
|
||||
else:
|
||||
xPosition -= self.__LIMBSIZE
|
||||
compensation = min(compensation, 0)
|
||||
|
||||
yPosition += compensation
|
||||
else:
|
||||
rotation = random.randint(-15, 15)
|
||||
yPosition = self.__CIRCLESIZE+self.__BODYSIZE-self.__LINEWIDTH
|
||||
if limb == "rl":
|
||||
limbDrawing = limbDrawing.rotate(rotation-45, expand=1)
|
||||
else:
|
||||
xPosition += -limbDrawing.size[0]+self.__LINEWIDTH*3
|
||||
limbDrawing = limbDrawing.rotate(rotation+45, expand=1)
|
||||
|
||||
pastePosition = (xPosition, yPosition)
|
||||
background.paste(limbDrawing, pastePosition, limbDrawing)
|
||||
|
||||
return background
|
||||
|
||||
def __badText(self, text: str, big: bool, color: tuple = (0, 0, 0, 255)):
|
||||
if big:
|
||||
font = self.__FONT
|
||||
else:
|
||||
font = self.__SMALLFONT
|
||||
w, h = font.getsize(text)
|
||||
img = Image.new("RGBA", (w, h), color=(0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img, "RGBA")
|
||||
|
||||
d.text((0, 0), text, font=font, fill=color)
|
||||
return img
|
||||
|
||||
def __drawGallows(self):
|
||||
gallowSize = (self.__GALLOWX, self.__GALLOWY)
|
||||
background = Image.new("RGBA", gallowSize, color=(0, 0, 0, 0))
|
||||
|
||||
bottomLine = self.__badLine(int(self.__GALLOWX * 0.75), True)
|
||||
bottomLineX = int(self.__GALLOWX * 0.125)
|
||||
bottomLineY = self.__GALLOWY-(self.__LINEWIDTH*4)
|
||||
pastePosition = (bottomLineX, bottomLineY)
|
||||
background.paste(bottomLine, pastePosition, bottomLine)
|
||||
|
||||
lineTwo = self.__badLine(self.__GALLOWY-self.__LINEWIDTH*6)
|
||||
lineTwoX = int(self.__GALLOWX*(0.75*self.__PHI))
|
||||
lineTwoY = self.__LINEWIDTH*2
|
||||
pastePosition = (lineTwoX, lineTwoY)
|
||||
background.paste(lineTwo, pastePosition, lineTwo)
|
||||
|
||||
topLine = self.__badLine(int(self.__GALLOWY*0.30), True)
|
||||
pasteX = int(self.__GALLOWX*(0.75*self.__PHI))-self.__LINEWIDTH
|
||||
pastePosition = (pasteX, self.__LINEWIDTH*3)
|
||||
background.paste(topLine, pastePosition, topLine)
|
||||
|
||||
lastLine = self.__badLine(int(self.__GALLOWY*0.125))
|
||||
pasteX += int(self.__GALLOWY*0.30)
|
||||
background.paste(lastLine, (pasteX, self.__LINEWIDTH*3), lastLine)
|
||||
return background
|
||||
|
||||
def __drawLetterLines(self, word: str, guessed: list, misses: int):
|
||||
letterWidth = self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE
|
||||
imageWidth = letterWidth*len(word)
|
||||
imageSize = (imageWidth, self.__LETTERLINELENGTH+self.__LINEWIDTH*3)
|
||||
letterLines = Image.new("RGBA", imageSize, color=(0, 0, 0, 0))
|
||||
for x, letter in enumerate(word):
|
||||
line = self.__badLine(self.__LETTERLINELENGTH, True)
|
||||
pasteX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
|
||||
pastePosition = (pasteX, self.__LETTERLINELENGTH)
|
||||
letterLines.paste(line, pastePosition, line)
|
||||
if guessed[x]:
|
||||
letterDrawing = self.__badText(letter, True)
|
||||
letterWidth = self.__FONT.getsize(letter)[0]
|
||||
letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
|
||||
letterX -= (letterWidth//2)
|
||||
letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2)
|
||||
letterLines.paste(letterDrawing, (letterX, 0), letterDrawing)
|
||||
elif misses == 6:
|
||||
letterDrawing = self.__badText(letter, True, (242, 66, 54))
|
||||
letterWidth = self.__FONT.getsize(letter)[0]
|
||||
letterX = x*(self.__LETTERLINELENGTH+self.__LETTERLINEDISTANCE)
|
||||
letterX -= (letterWidth//2)
|
||||
letterX += (self.__LETTERLINELENGTH//2)+(self.__LINEWIDTH*2)
|
||||
letterLines.paste(letterDrawing, (letterX, 0), letterDrawing)
|
||||
|
||||
return letterLines
|
||||
|
||||
def __shortestDist(self, positions: list, newPosition: tuple):
|
||||
__shortestDist = math.inf
|
||||
x, y = newPosition
|
||||
for i, j in positions:
|
||||
xDistance = abs(i-x)
|
||||
yDistance = abs(j-y)
|
||||
dist = math.sqrt(xDistance**2+yDistance**2)
|
||||
if __shortestDist > dist:
|
||||
__shortestDist = dist
|
||||
return __shortestDist
|
||||
|
||||
def __drawMisses(self, guesses: list, word: str):
|
||||
background = Image.new("RGBA", (600, 400), color=(0, 0, 0, 0))
|
||||
pos = []
|
||||
for guess in guesses:
|
||||
if guess not in word:
|
||||
placed = False
|
||||
while not placed:
|
||||
letter = self.__badText(guess, True)
|
||||
w, h = self.__FONT.getsize(guess)
|
||||
x = random.randint(0, 600-w)
|
||||
y = random.randint(0, 400-h)
|
||||
if self.__shortestDist(pos, (x, y)) > 70:
|
||||
pos.append((x, y))
|
||||
background.paste(letter, (x, y), letter)
|
||||
placed = True
|
||||
return background
|
||||
|
||||
def drawImage(self, channel: str):
|
||||
"""
|
||||
Draw a hangman Image.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
channel: str
|
||||
The id of the channel the game is in.
|
||||
"""
|
||||
self.__bot.log("Drawing hangman image", channel)
|
||||
game = self.__bot.database["hangman games"].find_one({"_id": channel})
|
||||
|
||||
random.seed(game["game ID"])
|
||||
|
||||
background = Image.open("gwendolyn/resources/paper.jpg")
|
||||
gallow = self.__drawGallows()
|
||||
man = self.__drawMan(game["misses"], game["game ID"])
|
||||
|
||||
random.seed(game["game ID"])
|
||||
letterLineParams = [game["word"], game["guessed"], game["misses"]]
|
||||
letterLines = self.__drawLetterLines(*letterLineParams)
|
||||
|
||||
random.seed(game["game ID"])
|
||||
misses = self.__drawMisses(game["guessed letters"], game["word"])
|
||||
|
||||
background.paste(gallow, (100, 100), gallow)
|
||||
background.paste(man, (300, 210), man)
|
||||
background.paste(letterLines, (120, 840), letterLines)
|
||||
background.paste(misses, (600, 150), misses)
|
||||
|
||||
missesText = self.__badText("MISSES", False)
|
||||
missesTextWidth = missesText.size[0]
|
||||
background.paste(missesText, (850-missesTextWidth//2, 50), missesText)
|
||||
|
||||
boardPath = f"gwendolyn/resources/games/hangman_boards/hangman_board{channel}.png"
|
||||
background.save(boardPath)
|
643
gwendolyn/funcs/games/hex.py
Normal file
643
gwendolyn/funcs/games/hex.py
Normal file
@ -0,0 +1,643 @@
|
||||
import random
|
||||
import copy
|
||||
import math
|
||||
import discord
|
||||
import math
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
class HexGame():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.draw = DrawHex(bot)
|
||||
self.BOARDWIDTH = 11
|
||||
self.ALLPOSITIONS = [(i,j) for i in range(11) for j in range(11)]
|
||||
self.ALLSET = set(self.ALLPOSITIONS)
|
||||
self.EMPTYDIJKSTRA = {}
|
||||
for position in self.ALLPOSITIONS:
|
||||
self.EMPTYDIJKSTRA[position] = math.inf # an impossibly high number
|
||||
self.HEXDIRECTIONS = [(0,1),(-1,1),(-1,0),(0,-1),(1,-1),(1,0)]
|
||||
|
||||
async def surrender(self, ctx):
|
||||
channel = str(ctx.channel_id)
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
user = f"#{ctx.author.id}"
|
||||
players = game["players"]
|
||||
|
||||
if user not in players:
|
||||
await ctx.send("You can't surrender when you're not a player.")
|
||||
else:
|
||||
opponent = (players.index(user) + 1) % 2
|
||||
opponentName = self.bot.database_funcs.get_name(players[opponent])
|
||||
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":opponent + 1}})
|
||||
await ctx.send(f"{ctx.author.display_name} surrendered")
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
|
||||
old_image = await ctx.channel.fetch_message(int(f.read()))
|
||||
|
||||
if old_image is not None:
|
||||
await old_image.delete()
|
||||
else:
|
||||
self.bot.log("The old image was already deleted")
|
||||
|
||||
self.bot.log("Sending the image")
|
||||
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
|
||||
old_image = await ctx.channel.send(file = discord.File(file_path))
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
|
||||
f.write(str(old_image.id))
|
||||
|
||||
self.bot.database["hex games"].delete_one({"_id":channel})
|
||||
|
||||
# Swap
|
||||
async def swap(self, ctx):
|
||||
channel = str(ctx.channel_id)
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
user = f"#{ctx.author.id}"
|
||||
|
||||
if game is None:
|
||||
await ctx.send("You can't swap nothing")
|
||||
elif user not in game["players"]:
|
||||
await ctx.send("You're not in the game")
|
||||
elif len(game["gameHistory"]) != 1: # Only after the first move
|
||||
await ctx.send("You can only swap as the second player after the very first move.")
|
||||
elif user != game["players"][game["turn"]-1]:
|
||||
await ctx.send("You can only swap after your opponent has placed their piece")
|
||||
else:
|
||||
self.bot.database["hex games"].update_one({"_id":channel},
|
||||
{"$set":{"players":game["players"][::-1]}}) # Swaps their player-number
|
||||
|
||||
# Swaps the color of the hexes on the board drawing:
|
||||
self.draw.drawSwap(channel)
|
||||
|
||||
opponent = game["players"][::-1][game["turn"]-1]
|
||||
gwendoTurn = (opponent == f"#{self.bot.user.id}")
|
||||
opponentName = self.bot.database_funcs.get_name(opponent)
|
||||
await ctx.send(f"The color of the players were swapped. It is now {opponentName}'s turn")
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
|
||||
old_image = await ctx.channel.fetch_message(int(f.read()))
|
||||
|
||||
if old_image is not None:
|
||||
await old_image.delete()
|
||||
else:
|
||||
self.bot.log("The old image was already deleted")
|
||||
|
||||
self.bot.log("Sending the image")
|
||||
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
|
||||
old_image = await ctx.channel.send(file = discord.File(file_path))
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
|
||||
f.write(str(old_image.id))
|
||||
|
||||
if gwendoTurn:
|
||||
await self.hexAI(ctx)
|
||||
|
||||
# Starts the game
|
||||
async def start(self, ctx, opponent):
|
||||
await self.bot.defer(ctx)
|
||||
user = f"#{ctx.author.id}"
|
||||
channel = str(ctx.channel_id)
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
|
||||
startedGame = False
|
||||
canStart = True
|
||||
|
||||
if game != None:
|
||||
sendMessage = "There's already a hex game going on in this channel"
|
||||
log_message = "There was already a game going on"
|
||||
canStart = False
|
||||
else:
|
||||
if type(opponent) == int:
|
||||
# Opponent is Gwendolyn
|
||||
if opponent in range(1, 6):
|
||||
opponentName = "Gwendolyn"
|
||||
difficulty = int(opponent)
|
||||
diffText = f" with difficulty {difficulty}"
|
||||
opponent = f"#{self.bot.user.id}"
|
||||
else:
|
||||
sendMessage = "Difficulty doesn't exist"
|
||||
log_message = "They tried to play against a difficulty that doesn't exist"
|
||||
canStart = False
|
||||
|
||||
elif type(opponent) == discord.member.Member:
|
||||
if opponent.bot:
|
||||
# User has challenged a bot
|
||||
if opponent == self.bot.user:
|
||||
# It was Gwendolyn
|
||||
opponentName = "Gwendolyn"
|
||||
difficulty = 2
|
||||
diffText = f" with difficulty {difficulty}"
|
||||
opponent = f"#{self.bot.user.id}"
|
||||
else:
|
||||
sendMessage = "You can't challenge a bot!"
|
||||
log_message = "They tried to challenge a bot"
|
||||
canStart = False
|
||||
else:
|
||||
# Opponent is another player
|
||||
if ctx.author != opponent:
|
||||
opponentName = opponent.display_name
|
||||
opponent = f"#{opponent.id}"
|
||||
difficulty = 5
|
||||
diffText = ""
|
||||
else:
|
||||
sendMessage = "You can't play against yourself"
|
||||
log_message = "They tried to play against themself"
|
||||
canStart = False
|
||||
else:
|
||||
canStart = False
|
||||
log_message = f"Opponent was neither int or member. It was {type(opponent)}"
|
||||
sendMessage = "Something went wrong"
|
||||
|
||||
if canStart:
|
||||
# board is 11x11
|
||||
board = [[0 for i in range(self.BOARDWIDTH)] for j in range(self.BOARDWIDTH)]
|
||||
players = [user, opponent]
|
||||
random.shuffle(players) # random starting player
|
||||
gameHistory = []
|
||||
|
||||
newGame = {"_id":channel,"board":board, "winner":0,
|
||||
"players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory}
|
||||
|
||||
self.bot.database["hex games"].insert_one(newGame)
|
||||
|
||||
# draw the board
|
||||
self.draw.drawBoard(channel)
|
||||
|
||||
gwendoTurn = (players[0] == f"#{self.bot.user.id}")
|
||||
startedGame = True
|
||||
|
||||
turnName = self.bot.database_funcs.get_name(players[0])
|
||||
sendMessage = f"Started Hex game against {opponentName}{diffText}. It's {turnName}'s turn"
|
||||
log_message = "Game started"
|
||||
|
||||
await ctx.send(sendMessage)
|
||||
self.bot.log(log_message)
|
||||
|
||||
if startedGame:
|
||||
file_path = f"gwendolyn/resources/games/hex_boards/board{ctx.channel_id}.png"
|
||||
newImage = await ctx.channel.send(file = discord.File(file_path))
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{ctx.channel_id}", "w") as f:
|
||||
f.write(str(newImage.id))
|
||||
|
||||
if gwendoTurn:
|
||||
await self.hexAI(ctx)
|
||||
|
||||
# Places a piece at the given location and checks things afterwards
|
||||
async def placeHex(self, ctx, position : str, user):
|
||||
channel = str(ctx.channel_id)
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
placedPiece = False
|
||||
|
||||
if game == None:
|
||||
sendMessage = "There's no game in this channel"
|
||||
self.bot.log("There was no game going on")
|
||||
elif not (position[0].isalpha() and position[1:].isnumeric() and len(position) in [2, 3]):
|
||||
sendMessage = "The position must be a letter followed by a number."
|
||||
self.bot.log(f"The position was not valid, {position}")
|
||||
else:
|
||||
players = game["players"]
|
||||
if user not in players:
|
||||
sendMessage = f"You can't place when you're not in the game. The game's players are: {self.bot.database_funcs.get_name(game['players'][0])} and {self.bot.database_funcs.get_name(game['players'][1])}."
|
||||
self.bot.log("They aren't in the game")
|
||||
elif players[game["turn"]-1] != user:
|
||||
sendMessage = "It's not your turn"
|
||||
self.bot.log("It wasn't their turn")
|
||||
else:
|
||||
player = game["turn"]
|
||||
turn = game["turn"]
|
||||
board = game["board"]
|
||||
|
||||
self.bot.log("Placing a piece on the board with placeHex()")
|
||||
# Places on board
|
||||
board = self.placeOnHexBoard(board,player,position)
|
||||
|
||||
if board is None:
|
||||
self.bot.log("It was an invalid position")
|
||||
sendMessage = ("That's an invalid position. You must place your piece on an empty field.")
|
||||
else:
|
||||
# If the move is valid:
|
||||
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"board":board}})
|
||||
turn = (turn % 2) + 1
|
||||
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"turn":turn}})
|
||||
|
||||
# Checking for a win
|
||||
self.bot.log("Checking for win")
|
||||
winner = self.evaluateBoard(game["board"])[1]
|
||||
|
||||
if winner == 0: # Continue with the game.
|
||||
gameWon = False
|
||||
sendMessage = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+". It's now "+self.bot.database_funcs.get_name(game["players"][turn-1])+"'s turn."# The score is "+str(score)
|
||||
|
||||
else: # Congratulations!
|
||||
gameWon = True
|
||||
self.bot.database["hex games"].update_one({"_id":channel},{"$set":{"winner":winner}})
|
||||
sendMessage = self.bot.database_funcs.get_name(game["players"][player-1])+" placed at "+position.upper()+" and won!"
|
||||
if game["players"][winner-1] != f"#{self.bot.user.id}":
|
||||
winAmount = game["difficulty"]*10
|
||||
sendMessage += " Adding "+str(winAmount)+" GwendoBucks to their account."
|
||||
|
||||
self.bot.database["hex games"].update_one({"_id":channel},
|
||||
{"$push":{"gameHistory":(int(position[1])-1, ord(position[0])-97)}})
|
||||
|
||||
# Is it now Gwendolyn's turn?
|
||||
gwendoTurn = False
|
||||
if game["players"][turn-1] == f"#{self.bot.user.id}":
|
||||
self.bot.log("It's Gwendolyn's turn")
|
||||
gwendoTurn = True
|
||||
|
||||
placedPiece = True
|
||||
|
||||
if user == f"#{self.bot.user.id}":
|
||||
await ctx.channel.send(sendMessage)
|
||||
else:
|
||||
await ctx.send(sendMessage)
|
||||
|
||||
if placedPiece:
|
||||
# Update the board
|
||||
self.draw.drawHexPlacement(channel,player, position)
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
|
||||
old_image = await ctx.channel.fetch_message(int(f.read()))
|
||||
|
||||
if old_image is not None:
|
||||
await old_image.delete()
|
||||
else:
|
||||
self.bot.log("The old image was already deleted")
|
||||
|
||||
self.bot.log("Sending the image")
|
||||
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
|
||||
old_image = await ctx.channel.send(file = discord.File(file_path))
|
||||
|
||||
if gameWon:
|
||||
self.bot.log("Dealing with the winning player")
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
|
||||
winner = game["winner"]
|
||||
if game["players"][winner-1] != f"#{self.bot.user.id}":
|
||||
winnings = game["difficulty"]*10
|
||||
self.bot.money.addMoney(game["players"][winner-1].lower(),winnings)
|
||||
|
||||
self.bot.database["hex games"].delete_one({"_id":channel})
|
||||
else:
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
|
||||
f.write(str(old_image.id))
|
||||
|
||||
if gwendoTurn:
|
||||
await self.hexAI(ctx)
|
||||
|
||||
# Returns a board where the placement has ocurred
|
||||
def placeOnHexBoard(self, board,player,position):
|
||||
# Translates the position
|
||||
position = position.lower()
|
||||
# Error handling
|
||||
column = ord(position[0]) - 97 # ord() translates from letter to number
|
||||
row = int(position[1:]) - 1
|
||||
if column not in range(self.BOARDWIDTH) or row not in range(self.BOARDWIDTH):
|
||||
self.bot.log("Position out of bounds")
|
||||
return None
|
||||
# Place at the position
|
||||
if board[row][column] == 0:
|
||||
board[row][column] = player
|
||||
return board
|
||||
else:
|
||||
self.bot.log("Cannot place on existing piece (error code 1532)")
|
||||
return None
|
||||
|
||||
# After your move, you have the option to undo get your turn back #TimeTravel
|
||||
async def undo(self, ctx):
|
||||
channel = str(ctx.channel_id)
|
||||
user = f"#{ctx.author.id}"
|
||||
undid = False
|
||||
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
|
||||
if user not in game["players"]:
|
||||
sendMessage = "You're not a player in the game"
|
||||
elif len(game["gameHistory"]) == 0:
|
||||
sendMessage = "You can't undo nothing"
|
||||
elif user != game["players"][(game["turn"] % 2)]: # If it's not your turn
|
||||
sendMessage = "It's not your turn"
|
||||
else:
|
||||
turn = game["turn"]
|
||||
self.bot.log("Undoing {}'s last move".format(self.bot.database_funcs.get_name(user)))
|
||||
|
||||
lastMove = game["gameHistory"].pop()
|
||||
game["board"][lastMove[0]][lastMove[1]] = 0
|
||||
self.bot.database["hex games"].update_one({"_id":channel},
|
||||
{"$set":{"board":game["board"]}})
|
||||
self.bot.database["hex games"].update_one({"_id":channel},
|
||||
{"$set":{"turn":turn%2 + 1}})
|
||||
|
||||
# Update the board
|
||||
self.draw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear
|
||||
sendMessage = f"You undid your last move at {lastMove}"
|
||||
undid = True
|
||||
|
||||
await ctx.send(sendMessage)
|
||||
if undid:
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "r") as f:
|
||||
old_image = await ctx.channel.fetch_message(int(f.read()))
|
||||
|
||||
if old_image is not None:
|
||||
await old_image.delete()
|
||||
else:
|
||||
self.bot.log("The old image was already deleted")
|
||||
|
||||
self.bot.log("Sending the image")
|
||||
file_path = f"gwendolyn/resources/games/hex_boards/board{channel}.png"
|
||||
old_image = await ctx.channel.send(file = discord.File(file_path))
|
||||
|
||||
with open(f"gwendolyn/resources/games/old_images/hex{channel}", "w") as f:
|
||||
f.write(str(old_image.id))
|
||||
|
||||
|
||||
# Plays as the AI
|
||||
async def hexAI(self, ctx):
|
||||
channel = str(ctx.channel_id)
|
||||
self.bot.log("Figuring out best move")
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
board = game["board"]
|
||||
|
||||
if len(game["gameHistory"]):
|
||||
lastMove = game["gameHistory"][-1]
|
||||
else:
|
||||
lastMove = (5,5)
|
||||
|
||||
# These moves are the last move +- 2.
|
||||
moves = [[(lastMove[0]+j-2,lastMove[1]+i-2) for i in range(5) if lastMove[1]+i-2 in range(11)] for j in range(5) if lastMove[0]+j-2 in range(11)]
|
||||
moves = sum(moves,[])
|
||||
movesCopy = moves.copy()
|
||||
for move in movesCopy:
|
||||
if board[move[0]][move[1]] != 0:
|
||||
moves.remove(move)
|
||||
chosenMove = random.choice(moves)
|
||||
|
||||
placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1)
|
||||
self.bot.log(f"ChosenMove is {chosenMove} at {placement}")
|
||||
|
||||
await self.placeHex(ctx, placement, f"#{self.bot.user.id}")
|
||||
|
||||
|
||||
def evaluateBoard(self, board):
|
||||
scores = {1:0, 2:0}
|
||||
winner = 0
|
||||
# Here, I use Dijkstra's algorithm to evaluate the board, as proposed by this article: https://towardsdatascience.com/hex-creating-intelligent-adversaries-part-2-heuristics-dijkstras-algorithm-597e4dcacf93
|
||||
for player in [1,2]:
|
||||
Distance = copy.deepcopy(self.EMPTYDIJKSTRA)
|
||||
# Initialize the starting hexes. For the blue player, this is the leftmost column. For the red player, this is the tom row.
|
||||
for start in (self.ALLPOSITIONS[::11] if player == 2 else self.ALLPOSITIONS[:11]):
|
||||
# An empty hex adds a of distance of 1. A hex of own color add distance 0. Opposite color adds infinite distance.
|
||||
Distance[start] = 1 if (board[start[0]][start[1]] == 0) else 0 if (board[start[0]][start[1]] == player) else math.inf
|
||||
visited = set() # Also called sptSet, short for "shortest path tree Set"
|
||||
for _ in range(self.BOARDWIDTH**2): # We can at most check every 121 hexes
|
||||
# Find the next un-visited hex, that has the lowest distance
|
||||
remainingHexes = self.ALLSET.difference(visited)
|
||||
A = [Distance[k] for k in remainingHexes] # Find the distance to each un-visited hex
|
||||
u = list(remainingHexes)[A.index(min(A))] # Chooses the one with the lowest distance
|
||||
|
||||
# Find neighbors of the hex u
|
||||
for di in self.HEXDIRECTIONS:
|
||||
v = (u[0] + di[0] , u[1] + di[1]) # v is a neighbor of u
|
||||
if v[0] in range(11) and v[1] in range(11) and v not in visited:
|
||||
new_dist = Distance[u] + (1 if (board[v[0]][v[1]] == 0) else 0 if (board[v[0]][v[1]] == player) else math.inf)
|
||||
Distance[v] = min(Distance[v], new_dist)
|
||||
# After a hex has been visited, this is noted
|
||||
visited.add(u)
|
||||
#self.bot.log("Distance from player {}'s start to {} is {}".format(player,u,Distance[u]))
|
||||
if u[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal
|
||||
scores[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent.
|
||||
break
|
||||
else:
|
||||
self.bot.log("For some reason, no path to the goal was found. ")
|
||||
if scores[player] == 0:
|
||||
winner = player
|
||||
break # We don't need to check the other player's score, if player1 won.
|
||||
return scores[2]-scores[1], winner
|
||||
|
||||
|
||||
def minimaxHex(self, board, depth, alpha, beta, maximizingPlayer):
|
||||
# The depth is how many moves ahead the computer checks. This value is the difficulty.
|
||||
if depth == 0 or 0 not in sum(board,[]):
|
||||
score = self.evaluateBoard(board)[0]
|
||||
return score
|
||||
# if final depth is not reached, look another move ahead:
|
||||
if maximizingPlayer: # red player predicts next move
|
||||
maxEval = -math.inf
|
||||
possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0]
|
||||
#self.bot.log("Judging a red move at depth {}".format(depth))
|
||||
for i in possiblePlaces:
|
||||
testBoard = copy.deepcopy(board)
|
||||
testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 1 # because maximizingPlayer is Red which is number 1
|
||||
evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,False)
|
||||
maxEval = max(maxEval, evaluation)
|
||||
alpha = max(alpha, evaluation)
|
||||
if beta <= alpha:
|
||||
#self.bot.log("Just pruned something!")
|
||||
break
|
||||
return maxEval
|
||||
else: # blue player predicts next move
|
||||
minEval = math.inf
|
||||
possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0]
|
||||
#self.bot.log("Judging a blue move at depth {}".format(depth))
|
||||
for i in possiblePlaces:
|
||||
testBoard = copy.deepcopy(board)
|
||||
testBoard[i // self.BOARDWIDTH][i % self.BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2
|
||||
evaluation = self.minimaxHex(testBoard,depth-1,alpha,beta,True)
|
||||
minEval = min(minEval, evaluation)
|
||||
beta = min(beta, evaluation)
|
||||
if beta <= alpha:
|
||||
#self.bot.log("Just pruned something!")
|
||||
break
|
||||
return minEval
|
||||
|
||||
class DrawHex():
|
||||
def __init__(self,bot):
|
||||
self.bot = bot
|
||||
# Defining all the variables
|
||||
self.CANVASWIDTH = 2400
|
||||
self.CANVASHEIGHT = 1800
|
||||
self.SIDELENGTH = 75
|
||||
|
||||
# The offsets centers the board in the picture
|
||||
self.XOFFSET = self.CANVASWIDTH/2 - 8*math.sqrt(3)*self.SIDELENGTH
|
||||
|
||||
# The offsets are the coordinates of the upperleft point in the
|
||||
# upperleftmost hexagon
|
||||
self.YOFFSET = self.CANVASHEIGHT/2 - 8*self.SIDELENGTH
|
||||
|
||||
# The whole width of one hexagon
|
||||
self.HEXAGONWIDTH = math.sqrt(3) * self.SIDELENGTH
|
||||
|
||||
# The height difference between two layers
|
||||
self.HEXAGONHEIGHT = 1.5 * self.SIDELENGTH
|
||||
self.FONTSIZE = 45
|
||||
self.TEXTCOLOR = (0,0,0)
|
||||
self.FONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.FONTSIZE)
|
||||
|
||||
self.LINETHICKNESS = 15
|
||||
self.HEXTHICKNESS = 6 # This is half the width of the background lining between every hex
|
||||
self.XTHICKNESS = self.HEXTHICKNESS * math.cos(math.pi/6)
|
||||
self.YTHICKNESS = self.HEXTHICKNESS * math.sin(math.pi/6)
|
||||
self.BACKGROUNDCOLOR = (230,230,230)
|
||||
self.BETWEENCOLOR = (231,231,231)
|
||||
self.BLANKCOLOR = "lightgrey"
|
||||
self.PIECECOLOR = {1:(237,41,57),2:(0,165,255),0:self.BLANKCOLOR} # player1 is red, player2 is blue
|
||||
self.BOARDCOORDINATES = [ [(self.XOFFSET + self.HEXAGONWIDTH*(column + row/2),self.YOFFSET + self.HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex
|
||||
self.COLHEXTHICKNESS = 4 # When placing a hex, it is a little bigger than the underlying hex in the background (4 < 6)
|
||||
self.COLXTHICKNESS = self.COLHEXTHICKNESS * math.cos(math.pi/6)
|
||||
self.COLYTHICKNESS = self.COLHEXTHICKNESS * math.sin(math.pi/6)
|
||||
# The Name display things:
|
||||
self.NAMESIZE = 60
|
||||
self.NAMEFONT = ImageFont.truetype('gwendolyn/resources/fonts/futura-bold.ttf', self.NAMESIZE)
|
||||
self.XNAME = {1:175, 2:self.CANVASWIDTH-100}
|
||||
self.YNAME = {1:self.CANVASHEIGHT-150, 2:150}
|
||||
self.NAMEHEXPADDING = 90
|
||||
self.SMALLWIDTH = self.HEXAGONWIDTH * 0.6
|
||||
self.SMALLSIDELENGTH = self.SIDELENGTH * 0.6
|
||||
|
||||
def drawBoard(self, channel):
|
||||
self.bot.log("Drawing empty Hex board")
|
||||
|
||||
# Creates the empty image
|
||||
im = Image.new('RGB', size=(self.CANVASWIDTH, self.CANVASHEIGHT),color = self.BACKGROUNDCOLOR)
|
||||
# 'd' is a shortcut to drawing on the image
|
||||
d = ImageDraw.Draw(im,"RGBA")
|
||||
|
||||
# Drawing all the hexagons
|
||||
for column in self.BOARDCOORDINATES:
|
||||
for startingPoint in column:
|
||||
x = startingPoint[0]
|
||||
y = startingPoint[1]
|
||||
d.polygon([
|
||||
(x, y),
|
||||
(x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH),
|
||||
(x+self.HEXAGONWIDTH, y),
|
||||
(x+self.HEXAGONWIDTH, y+self.SIDELENGTH),
|
||||
(x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH),
|
||||
(x, y+self.SIDELENGTH),
|
||||
],fill = self.BETWEENCOLOR)
|
||||
d.polygon([
|
||||
(x+self.XTHICKNESS, y + self.YTHICKNESS),
|
||||
(x+self.HEXAGONWIDTH/2, y-0.5*self.SIDELENGTH + self.HEXTHICKNESS),
|
||||
(x+self.HEXAGONWIDTH-self.XTHICKNESS, y + self.YTHICKNESS),
|
||||
(x+self.HEXAGONWIDTH-self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS),
|
||||
(x+self.HEXAGONWIDTH/2, y+1.5*self.SIDELENGTH - self.HEXTHICKNESS),
|
||||
(x+self.XTHICKNESS, y+self.SIDELENGTH - self.YTHICKNESS),
|
||||
],fill = self.BLANKCOLOR)
|
||||
|
||||
# Draw color on the outside of the board
|
||||
# Top line, red
|
||||
d.line(sum((sum([(point[0],point[1],point[0]+self.HEXAGONWIDTH/2,point[1]-self.HEXAGONHEIGHT+self.SIDELENGTH) for point in self.BOARDCOORDINATES[0]],()),(self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4)),()),
|
||||
fill = self.PIECECOLOR[1],width = self.LINETHICKNESS)
|
||||
# Bottom line, red
|
||||
d.line(sum(((self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4),sum([(point[0]+self.HEXAGONWIDTH/2,point[1]+self.HEXAGONHEIGHT,point[0]+self.HEXAGONWIDTH,point[1]+self.SIDELENGTH) for point in self.BOARDCOORDINATES[10]],())),()),
|
||||
fill = self.PIECECOLOR[1],width = self.LINETHICKNESS)
|
||||
# Left line, blue
|
||||
d.line(sum((sum([(row[0][0],row[0][1],row[0][0],row[0][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],()),(self.BOARDCOORDINATES[10][0][0]+self.HEXAGONWIDTH/4,self.BOARDCOORDINATES[10][0][1]+self.SIDELENGTH*5/4)),()),
|
||||
fill = self.PIECECOLOR[2],width = self.LINETHICKNESS)
|
||||
# Right line, blue
|
||||
d.line(sum(((self.BOARDCOORDINATES[0][10][0]+self.HEXAGONWIDTH*3/4,self.BOARDCOORDINATES[0][10][1]-self.SIDELENGTH/4),sum([(row[10][0]+self.HEXAGONWIDTH,row[10][1],row[10][0]+self.HEXAGONWIDTH,row[10][1]+self.SIDELENGTH) for row in self.BOARDCOORDINATES],())),()),
|
||||
fill = self.PIECECOLOR[2],width = self.LINETHICKNESS)
|
||||
|
||||
# Writes "abc..", "123.." on the columns and rows
|
||||
for i in range(11):
|
||||
# Top letters
|
||||
d.text( (self.XOFFSET + self.HEXAGONWIDTH*i, self.YOFFSET-66) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR)
|
||||
# Bottom letters
|
||||
d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i+11.5/2), self.YOFFSET - 15 + 11*self.HEXAGONHEIGHT) , "ABCDEFGHIJK"[i], font=self.FONT, fill=self.TEXTCOLOR)
|
||||
# Left numbers
|
||||
d.multiline_text( (self.XOFFSET + self.HEXAGONWIDTH*i/2 - 72 -4*(i>8), self.YOFFSET + 18 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR, align="right")
|
||||
# Right numbers
|
||||
d.text( (self.XOFFSET + self.HEXAGONWIDTH*(i/2+11) + 30 , self.YOFFSET + 6 + i*self.HEXAGONHEIGHT) , str(i+1), font=self.FONT, fill=self.TEXTCOLOR)
|
||||
|
||||
# Write player names and color
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
|
||||
for p in [1,2]:
|
||||
playername = self.bot.database_funcs.get_name(game["players"][p-1])
|
||||
# Draw name
|
||||
x = self.XNAME[p]
|
||||
x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned
|
||||
y = self.YNAME[p]
|
||||
d.text((x,y),playername, font=self.NAMEFONT, fill = self.TEXTCOLOR)
|
||||
# Draw a half-size Hexagon to indicate the player's color
|
||||
x -= self.NAMEHEXPADDING # To the left of both names
|
||||
d.polygon([
|
||||
(x, y),
|
||||
(x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2),
|
||||
(x+self.SMALLWIDTH, y),
|
||||
(x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH),
|
||||
(x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2),
|
||||
(x, y+self.SMALLSIDELENGTH),
|
||||
],fill = self.PIECECOLOR[p])
|
||||
|
||||
im.save("gwendolyn/resources/games/hex_boards/board"+channel+".png")
|
||||
|
||||
|
||||
|
||||
|
||||
def drawHexPlacement(self, channel,player,position):
|
||||
FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png"
|
||||
self.bot.log(f"Drawing a newly placed hex. Filename: board{channel}.png")
|
||||
|
||||
# Translates position
|
||||
# We don't need to error-check, because the position is already checked in placeOnHexBoard()
|
||||
position = position.lower()
|
||||
column = ord(position[0])-97 # ord() translates from letter to number
|
||||
row = int(position[1:])-1
|
||||
|
||||
# Find the coordinates for the filled hex drawing
|
||||
hex_coords = [
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS),
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]-0.5*self.SIDELENGTH + self.COLHEXTHICKNESS),
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1] + self.COLYTHICKNESS),
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH-self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS),
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.HEXAGONWIDTH/2, self.BOARDCOORDINATES[row][column][1]+1.5*self.SIDELENGTH - self.COLHEXTHICKNESS),
|
||||
(self.BOARDCOORDINATES[row][column][0]+self.COLXTHICKNESS, self.BOARDCOORDINATES[row][column][1]+self.SIDELENGTH - self.COLYTHICKNESS),
|
||||
]
|
||||
|
||||
# Opens the image
|
||||
try:
|
||||
with Image.open(FILEPATH) as im:
|
||||
d = ImageDraw.Draw(im,"RGBA")
|
||||
# Draws the hex piece
|
||||
d.polygon(hex_coords,fill = self.PIECECOLOR[player], outline = self.BETWEENCOLOR)
|
||||
|
||||
# Save
|
||||
im.save(FILEPATH)
|
||||
except:
|
||||
self.bot.log("Error drawing new hex on board (error code 1541")
|
||||
|
||||
def drawSwap(self, channel):
|
||||
FILEPATH = "gwendolyn/resources/games/hex_boards/board"+channel+".png"
|
||||
game = self.bot.database["hex games"].find_one({"_id":channel})
|
||||
# Opens the image
|
||||
try:
|
||||
with Image.open(FILEPATH) as im:
|
||||
d = ImageDraw.Draw(im,"RGBA")
|
||||
|
||||
# Write player names and color
|
||||
for p in [1,2]:
|
||||
playername = self.bot.database_funcs.get_name(game["players"][p%2])
|
||||
|
||||
x = self.XNAME[p]
|
||||
x -= self.NAMEFONT.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned
|
||||
y = self.YNAME[p]
|
||||
|
||||
# Draw a half-size Hexagon to indicate the player's color
|
||||
x -= self.NAMEHEXPADDING # To the left of both names
|
||||
d.polygon([
|
||||
(x, y),
|
||||
(x+self.SMALLWIDTH/2, y-self.SMALLSIDELENGTH/2),
|
||||
(x+self.SMALLWIDTH, y),
|
||||
(x+self.SMALLWIDTH, y+self.SMALLSIDELENGTH),
|
||||
(x+self.SMALLWIDTH/2, y+self.SMALLSIDELENGTH*3/2),
|
||||
(x, y+self.SMALLSIDELENGTH),
|
||||
],fill = self.PIECECOLOR[p % 2 + 1])
|
||||
|
||||
# Save
|
||||
im.save(FILEPATH)
|
||||
except:
|
||||
self.bot.log("Error drawing swap (error code 1542)")
|
286
gwendolyn/funcs/games/invest.py
Normal file
286
gwendolyn/funcs/games/invest.py
Normal file
@ -0,0 +1,286 @@
|
||||
"""
|
||||
Contains functions relating to invest commands.
|
||||
|
||||
*Classes*
|
||||
---------
|
||||
Invest
|
||||
Contains all the code for the invest commands.
|
||||
"""
|
||||
import discord # Used for embeds
|
||||
from discord_slash.context import SlashContext # Used for type hints
|
||||
|
||||
|
||||
class Invest():
|
||||
"""
|
||||
Contains all the invest functions.
|
||||
|
||||
*Methods*
|
||||
---------
|
||||
getPrice(symbol: str) -> int
|
||||
getPortfolio(user: str) -> str
|
||||
buyStock(user: str, stock: str: buyAmount: int) -> str
|
||||
sellStock(user: str, stock: str, buyAmount: int) -> str
|
||||
parseInvest(ctx: discord_slash.context.SlashContext,
|
||||
parameters: str)
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""Initialize the class."""
|
||||
self.bot = bot
|
||||
|
||||
def getPrice(self, symbol: str):
|
||||
"""
|
||||
Get the price of a stock.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
symbol: str
|
||||
The symbol of the stock to get the price of.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
price: int
|
||||
The price of the stock.
|
||||
"""
|
||||
res = self.bot.finnhub_client.quote(symbol.upper())
|
||||
if res == {}:
|
||||
return 0
|
||||
else:
|
||||
return int(res["c"] * 100)
|
||||
|
||||
def getPortfolio(self, user: str):
|
||||
"""
|
||||
Get the stock portfolio of a user.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
user: str
|
||||
The id of the user to get the portfolio of.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
portfolio: str
|
||||
The portfolio.
|
||||
"""
|
||||
investmentsDatabase = self.bot.database["investments"]
|
||||
userInvestments = investmentsDatabase.find_one({"_id": user})
|
||||
|
||||
user_name = self.bot.database_funcs.get_name(user)
|
||||
|
||||
if userInvestments in [None, {}]:
|
||||
return f"{user_name} does not have a stock portfolio."
|
||||
else:
|
||||
portfolio = f"**Stock portfolio for {user_name}**"
|
||||
|
||||
for key, value in list(userInvestments["investments"].items()):
|
||||
purchaseValue = value["purchased for"]
|
||||
stockPrice = self.getPrice(key)
|
||||
valueAtPurchase = value["value at purchase"]
|
||||
purchasedStock = value["purchased"]
|
||||
valueChange = (stockPrice / valueAtPurchase)
|
||||
currentValue = int(valueChange * purchasedStock)
|
||||
portfolio += f"\n**{key}**: ___{currentValue} GwendoBucks___"
|
||||
|
||||
if purchaseValue != "?":
|
||||
portfolio += f" (purchased for {purchaseValue})"
|
||||
|
||||
return portfolio
|
||||
|
||||
def buyStock(self, user: str, stock: str, buyAmount: int):
|
||||
"""
|
||||
Buy an amount of a specific stock.
|
||||
|
||||
*Paramaters*
|
||||
------------
|
||||
user: str
|
||||
The id of the user buying.
|
||||
stock: str
|
||||
The symbol of the stock to buy.
|
||||
buyAmount: int
|
||||
The amount of GwendoBucks to use to buy the stock.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
sendMessage: str
|
||||
The message to return to the user.
|
||||
"""
|
||||
if buyAmount < 100:
|
||||
return "You cannot buy stocks for less than 100 GwendoBucks"
|
||||
elif self.bot.money.checkBalance(user) < buyAmount:
|
||||
return "You don't have enough money for that"
|
||||
elif self.getPrice(stock) <= 0:
|
||||
return f"{stock} is not traded on the american market."
|
||||
else:
|
||||
investmentsDatabase = self.bot.database["investments"]
|
||||
stockPrice = self.getPrice(stock)
|
||||
userInvestments = investmentsDatabase.find_one({"_id": user})
|
||||
|
||||
self.bot.money.addMoney(user, -1*buyAmount)
|
||||
stock = stock.upper()
|
||||
|
||||
if userInvestments is not None:
|
||||
userInvestments = userInvestments["investments"]
|
||||
if stock in userInvestments:
|
||||
value = userInvestments[stock]
|
||||
valueChange = (stockPrice / value["value at purchase"])
|
||||
currentValue = int(valueChange * value["purchased"])
|
||||
newAmount = currentValue + buyAmount
|
||||
|
||||
valuePath = f"investments.{stock}.value at purchase"
|
||||
updater = {"$set": {valuePath: stockPrice}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
|
||||
purchasedPath = f"investments.{stock}.purchased"
|
||||
updater = {"$set": {purchasedPath: newAmount}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
|
||||
if value["purchased for"] != "?":
|
||||
purchasedForPath = f"investments.{stock}.purchased for"
|
||||
updater = {"$set": {purchasedForPath: buyAmount}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
else:
|
||||
updater = {
|
||||
"$set": {
|
||||
"investments.{stock}": {
|
||||
"purchased": buyAmount,
|
||||
"value at purchase": stockPrice,
|
||||
"purchased for": buyAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
else:
|
||||
newUser = {
|
||||
"_id": user,
|
||||
"investments": {
|
||||
stock: {
|
||||
"purchased": buyAmount,
|
||||
"value at purchase": stockPrice,
|
||||
"purchased for": buyAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
investmentsDatabase.insert_one(newUser)
|
||||
|
||||
user_name = self.bot.database_funcs.get_name(user)
|
||||
sendMessage = "{} bought {} GwendoBucks worth of {} stock"
|
||||
sendMessage = sendMessage.format(user_name, buyAmount, stock)
|
||||
return sendMessage
|
||||
|
||||
def sellStock(self, user: str, stock: str, sellAmount: int):
|
||||
"""
|
||||
Sell an amount of a specific stock.
|
||||
|
||||
*Paramaters*
|
||||
------------
|
||||
user: str
|
||||
The id of the user selling.
|
||||
stock: str
|
||||
The symbol of the stock to sell.
|
||||
buyAmount: int
|
||||
The amount of GwendoBucks to sell for.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
sendMessage: str
|
||||
The message to return to the user.
|
||||
"""
|
||||
if sellAmount <= 0:
|
||||
return "no"
|
||||
else:
|
||||
investmentsDatabase = self.bot.database["investments"]
|
||||
userData = investmentsDatabase.find_one({"_id": user})
|
||||
userInvestments = userData["investments"]
|
||||
|
||||
stock = stock.upper()
|
||||
|
||||
if userInvestments is not None and stock in userInvestments:
|
||||
value = userInvestments[stock]
|
||||
stockPrice = self.getPrice(stock)
|
||||
priceChange = (stockPrice / value["value at purchase"])
|
||||
purchasedAmount = int(priceChange * value["purchased"])
|
||||
purchasedPath = f"investments.{stock}.purchased"
|
||||
updater = {"$set": {purchasedPath: purchasedAmount}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
valueAtPurchasePath = f"investments.{stock}.value at purchase"
|
||||
updater = {"$set": {valueAtPurchasePath: stockPrice}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
if value["purchased"] >= sellAmount:
|
||||
self.bot.money.addMoney(user, sellAmount)
|
||||
if sellAmount < value["purchased"]:
|
||||
purchasedPath = f"investments.{stock}.purchased"
|
||||
updater = {"$inc": {purchasedPath: -sellAmount}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
|
||||
purchasedForPath = f"investments.{stock}.purchased for"
|
||||
updater = {"$set": {purchasedForPath: "?"}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
else:
|
||||
updater = {"$unset": {f"investments.{stock}": ""}}
|
||||
investmentsDatabase.update_one({"_id": user}, updater)
|
||||
|
||||
user_name = self.bot.database_funcs.get_name(user)
|
||||
sendMessage = "{} sold {} GwendoBucks worth of {} stock"
|
||||
return sendMessage.format(user_name, sellAmount, stock)
|
||||
else:
|
||||
return f"You don't have enough {stock} stocks to do that"
|
||||
else:
|
||||
return f"You don't have any {stock} stock"
|
||||
|
||||
async def parseInvest(self, ctx: SlashContext, parameters: str):
|
||||
"""
|
||||
Parse an invest command. TO BE DELETED.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: discord_slash.context.SlashContext
|
||||
The context of the slash command.
|
||||
parameters: str
|
||||
The parameters of the command.
|
||||
"""
|
||||
await self.bot.defer(ctx)
|
||||
user = f"#{ctx.author.id}"
|
||||
|
||||
if parameters.startswith("check"):
|
||||
commands = parameters.split(" ")
|
||||
if len(commands) == 1:
|
||||
response = self.getPortfolio(user)
|
||||
else:
|
||||
price = self.getPrice(commands[1])
|
||||
if price == 0:
|
||||
response = "{} is not traded on the american market."
|
||||
response = response.format(commands[0].upper())
|
||||
else:
|
||||
price = f"{price:,}".replace(",", ".")
|
||||
response = self.bot.long_strings["Stock value"]
|
||||
response = response.format(commands[1].upper(), price)
|
||||
|
||||
elif parameters.startswith("buy"):
|
||||
commands = parameters.split(" ")
|
||||
if len(commands) == 3:
|
||||
response = self.buyStock(user, commands[1], int(commands[2]))
|
||||
else:
|
||||
response = self.bot.long_strings["Stock parameters"]
|
||||
|
||||
elif parameters.startswith("sell"):
|
||||
commands = parameters.split(" ")
|
||||
if len(commands) == 3:
|
||||
response = self.sellStock(user, commands[1], int(commands[2]))
|
||||
else:
|
||||
response = self.bot.long_strings["Stock parameters"]
|
||||
|
||||
else:
|
||||
response = "Incorrect parameters"
|
||||
|
||||
if response.startswith("**"):
|
||||
responses = response.split("\n")
|
||||
text = "\n".join(responses[1:])
|
||||
embedParams = {
|
||||
"title": responses[0],
|
||||
"description": text,
|
||||
"colour": 0x00FF00
|
||||
}
|
||||
em = discord.Embed(**embedParams)
|
||||
await ctx.send(embed=em)
|
||||
else:
|
||||
await ctx.send(response)
|
151
gwendolyn/funcs/games/money.py
Normal file
151
gwendolyn/funcs/games/money.py
Normal file
@ -0,0 +1,151 @@
|
||||
"""
|
||||
Contains the code that deals with money.
|
||||
|
||||
*Classes*
|
||||
---------
|
||||
Money
|
||||
Deals with money.
|
||||
"""
|
||||
import discord_slash # Used for typehints
|
||||
import discord # Used for typehints
|
||||
|
||||
|
||||
class Money():
|
||||
"""
|
||||
Deals with money.
|
||||
|
||||
*Methods*
|
||||
---------
|
||||
checkBalance(user: str)
|
||||
sendBalance(ctx: discord_slash.context.SlashContext)
|
||||
addMoney(user: str, amount: int)
|
||||
giveMoney(ctx: discord_slash.context.SlashContext, user: discord.User,
|
||||
amount: int)
|
||||
|
||||
*Attributes*
|
||||
------------
|
||||
bot: Gwendolyn
|
||||
The instance of Gwendolyn
|
||||
database: pymongo.Client
|
||||
The mongo database
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""Initialize the class."""
|
||||
self.bot = bot
|
||||
self.database = bot.database
|
||||
|
||||
def checkBalance(self, user: str):
|
||||
"""
|
||||
Get the account balance of a user.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
user: str
|
||||
The user to get the balance of.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
balance: int
|
||||
The balance of the user's account.
|
||||
"""
|
||||
self.bot.log("checking "+user+"'s account balance")
|
||||
|
||||
userData = self.database["users"].find_one({"_id": user})
|
||||
|
||||
if userData is not None:
|
||||
return userData["money"]
|
||||
else:
|
||||
return 0
|
||||
|
||||
async def sendBalance(self, ctx: discord_slash.context.SlashContext):
|
||||
"""
|
||||
Get your own account balance.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: discord_slash.context.SlashContext
|
||||
The context of the command.
|
||||
"""
|
||||
await self.bot.defer(ctx)
|
||||
response = self.checkBalance("#"+str(ctx.author.id))
|
||||
user_name = ctx.author.display_name
|
||||
if response == 1:
|
||||
new_message = f"{user_name} has {response} GwendoBuck"
|
||||
else:
|
||||
new_message = f"{user_name} has {response} GwendoBucks"
|
||||
await ctx.send(new_message)
|
||||
|
||||
# Adds money to the account of a user
|
||||
def addMoney(self, user: str, amount: int):
|
||||
"""
|
||||
Add money to a user account.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
user: str
|
||||
The id of the user to give money.
|
||||
amount: int
|
||||
The amount to add to the user's account.
|
||||
"""
|
||||
self.bot.log("adding "+str(amount)+" to "+user+"'s account")
|
||||
|
||||
userData = self.database["users"].find_one({"_id": user})
|
||||
|
||||
if userData is not None:
|
||||
updater = {"$inc": {"money": amount}}
|
||||
self.database["users"].update_one({"_id": user}, updater)
|
||||
else:
|
||||
newUser = {
|
||||
"_id": user,
|
||||
"user name": self.bot.database_funcs.get_name(user),
|
||||
"money": amount
|
||||
}
|
||||
self.database["users"].insert_one(newUser)
|
||||
|
||||
# Transfers money from one user to another
|
||||
async def giveMoney(self, ctx: discord_slash.context.SlashContext,
|
||||
user: discord.User, amount: int):
|
||||
"""
|
||||
Give someone else money from your account.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: discord_slash.context.SlashContext
|
||||
The context of the command.
|
||||
user: discord.User
|
||||
The user to give money.
|
||||
amount: int
|
||||
The amount to transfer.
|
||||
"""
|
||||
await self.bot.defer(ctx)
|
||||
username = user.display_name
|
||||
if self.bot.database_funcs.get_id(username) is None:
|
||||
async for member in ctx.guild.fetch_members(limit=None):
|
||||
if member.display_name.lower() == username.lower():
|
||||
username = member.display_name
|
||||
user_id = f"#{member.id}"
|
||||
newUser = {
|
||||
"_id": user_id,
|
||||
"user name": username,
|
||||
"money": 0
|
||||
}
|
||||
self.bot.database["users"].insert_one(newUser)
|
||||
|
||||
userid = f"#{ctx.author.id}"
|
||||
userData = self.database["users"].find_one({"_id": userid})
|
||||
targetUser = self.bot.database_funcs.get_id(username)
|
||||
|
||||
if amount <= 0:
|
||||
self.bot.log("They tried to steal")
|
||||
await ctx.send("Yeah, no. You can't do that")
|
||||
elif targetUser is None:
|
||||
self.bot.log("They weren't in the system")
|
||||
await ctx.send("The target doesn't exist")
|
||||
elif userData is None or userData["money"] < amount:
|
||||
self.bot.log("They didn't have enough GwendoBucks")
|
||||
await ctx.send("You don't have that many GwendoBuck")
|
||||
else:
|
||||
self.addMoney(f"#{ctx.author.id}", -1 * amount)
|
||||
self.addMoney(targetUser, amount)
|
||||
await ctx.send(f"Transferred {amount} GwendoBucks to {username}")
|
205
gwendolyn/funcs/games/trivia.py
Normal file
205
gwendolyn/funcs/games/trivia.py
Normal file
@ -0,0 +1,205 @@
|
||||
"""
|
||||
Contains code for trivia games.
|
||||
|
||||
*Classes*
|
||||
---------
|
||||
Trivia
|
||||
Contains all the code for the trivia commands.
|
||||
"""
|
||||
import urllib # Used to get data from api
|
||||
import json # Used to read data from api
|
||||
import random # Used to shuffle answers
|
||||
import asyncio # Used to sleep
|
||||
|
||||
from discord_slash.context import SlashContext # Used for type hints
|
||||
|
||||
|
||||
class Trivia():
|
||||
"""
|
||||
Contains the code for trivia games.
|
||||
|
||||
*Methods*
|
||||
---------
|
||||
triviaStart(channel: str) -> str, str, str
|
||||
triviaAnswer(user: str, channel: str, command: str) -> str
|
||||
triviaCountPoints(channel: str)
|
||||
triviaParse(ctx: SlashContext, answer: str)
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
"""Initialize the class."""
|
||||
self.bot = bot
|
||||
|
||||
def triviaStart(self, channel: str):
|
||||
"""
|
||||
Start a game of trivia.
|
||||
|
||||
Downloads a question with answers, shuffles the wrong answers
|
||||
with the correct answer and returns the questions and answers.
|
||||
Also saves the question in the database.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
channel: str
|
||||
The id of the channel to start the game in
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
sendMessage: str
|
||||
The message to return to the user.
|
||||
"""
|
||||
triviaQuestions = self.bot.database["trivia questions"]
|
||||
question = triviaQuestions.find_one({"_id": channel})
|
||||
|
||||
self.bot.log(f"Trying to find a trivia question for {channel}")
|
||||
|
||||
if question is None:
|
||||
apiUrl = "https://opentdb.com/api.php?amount=10&type=multiple"
|
||||
with urllib.request.urlopen(apiUrl) as response:
|
||||
data = json.loads(response.read())
|
||||
|
||||
question = data["results"][0]["question"]
|
||||
self.bot.log(f"Found the question \"{question}\"")
|
||||
answers = data["results"][0]["incorrect_answers"]
|
||||
answers.append(data["results"][0]["correct_answer"])
|
||||
random.shuffle(answers)
|
||||
correctAnswer = data["results"][0]["correct_answer"]
|
||||
correctAnswer = answers.index(correctAnswer) + 97
|
||||
|
||||
newQuestion = {
|
||||
"_id": channel,
|
||||
"answer": str(chr(correctAnswer)),
|
||||
"players": {}
|
||||
}
|
||||
triviaQuestions.insert_one(newQuestion)
|
||||
|
||||
replacements = {
|
||||
"'": "\'",
|
||||
""": "\"",
|
||||
"“": "\"",
|
||||
"”": "\"",
|
||||
"é": "é"
|
||||
}
|
||||
question = data["results"][0]["question"]
|
||||
|
||||
for key, value in replacements.items():
|
||||
question = question.replace(key, value)
|
||||
|
||||
for answer in answers:
|
||||
for key, value in replacements.items():
|
||||
answer = answer.replace(key, value)
|
||||
|
||||
return question, answers, correctAnswer
|
||||
else:
|
||||
log_message = "There was already a trivia question for that channel"
|
||||
self.bot.log(log_message)
|
||||
return self.bot.long_strings["Trivia going on"], "", ""
|
||||
|
||||
def triviaAnswer(self, user: str, channel: str, command: str):
|
||||
"""
|
||||
Answer the current trivia question.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
user: str
|
||||
The id of the user who answered.
|
||||
channel: str
|
||||
The id of the channel the game is in.
|
||||
command: str
|
||||
The user's answer.
|
||||
|
||||
*Returns*
|
||||
---------
|
||||
sendMessage: str
|
||||
The message to send if the function failed.
|
||||
"""
|
||||
triviaQuestions = self.bot.database["trivia questions"]
|
||||
question = triviaQuestions.find_one({"_id": channel})
|
||||
|
||||
if command not in ["a", "b", "c", "d"]:
|
||||
self.bot.log("I didn't quite understand that")
|
||||
return "I didn't quite understand that"
|
||||
elif question is None:
|
||||
self.bot.log("There's no question right now")
|
||||
return "There's no question right now"
|
||||
elif user in question["players"]:
|
||||
self.bot.log(f"{user} has already answered this question")
|
||||
return f"{user} has already answered this question"
|
||||
else:
|
||||
self.bot.log(f"{user} answered the question in {channel}")
|
||||
|
||||
updater = {"$set": {f"players.{user}": command}}
|
||||
triviaQuestions.update_one({"_id": channel}, updater)
|
||||
return None
|
||||
|
||||
def triviaCountPoints(self, channel: str):
|
||||
"""
|
||||
Add money to every winner's account.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
channel: str
|
||||
The id of the channel the game is in.
|
||||
"""
|
||||
triviaQuestions = self.bot.database["trivia questions"]
|
||||
question = triviaQuestions.find_one({"_id": channel})
|
||||
|
||||
self.bot.log("Counting points for question in "+channel)
|
||||
|
||||
if question is not None:
|
||||
for player, answer in question["players"].items():
|
||||
if answer == question["answer"]:
|
||||
self.bot.money.addMoney(player, 1)
|
||||
else:
|
||||
self.bot.log("Couldn't find the questio")
|
||||
|
||||
return None
|
||||
|
||||
async def triviaParse(self, ctx: SlashContext, answer: str):
|
||||
"""
|
||||
Parse a trivia command.
|
||||
|
||||
*Parameters*
|
||||
------------
|
||||
ctx: SlashContext
|
||||
The context of the command.
|
||||
answer: str
|
||||
The answer, if any.
|
||||
"""
|
||||
await self.bot.defer(ctx)
|
||||
channelId = str(ctx.channel_id)
|
||||
if answer == "":
|
||||
question, options, correctAnswer = self.triviaStart(channelId)
|
||||
if options != "":
|
||||
results = "**"+question+"**\n"
|
||||
for x, option in enumerate(options):
|
||||
results += chr(x+97) + ") "+option+"\n"
|
||||
|
||||
await ctx.send(results)
|
||||
|
||||
await asyncio.sleep(60)
|
||||
|
||||
self.triviaCountPoints(channelId)
|
||||
|
||||
delete_gameParams = ["trivia questions", channelId]
|
||||
self.bot.database_funcs.delete_game(*delete_gameParams)
|
||||
|
||||
self.bot.log("Time's up for the trivia question", channelId)
|
||||
sendMessage = self.bot.long_strings["Trivia time up"]
|
||||
formatParams = [chr(correctAnswer), options[correctAnswer-97]]
|
||||
sendMessage = sendMessage.format(*formatParams)
|
||||
await ctx.send(sendMessage)
|
||||
else:
|
||||
await ctx.send(question, hidden=True)
|
||||
|
||||
elif answer in ["a", "b", "c", "d"]:
|
||||
userId = f"#{ctx.author.id}"
|
||||
response = self.triviaAnswer(userId, channelId, answer)
|
||||
if response is None:
|
||||
user_name = ctx.author.display_name
|
||||
await ctx.send(f"{user_name} answered **{answer}**")
|
||||
else:
|
||||
await ctx.send(response)
|
||||
else:
|
||||
self.bot.log("I didn't understand that", channelId)
|
||||
await ctx.send("I didn't understand that")
|
5
gwendolyn/funcs/lookup/__init__.py
Normal file
5
gwendolyn/funcs/lookup/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Gwendolyn functions for looking things up."""
|
||||
|
||||
__all__ = ["LookupFuncs"]
|
||||
|
||||
from .lookup_funcs import LookupFuncs
|
164
gwendolyn/funcs/lookup/lookup_funcs.py
Normal file
164
gwendolyn/funcs/lookup/lookup_funcs.py
Normal file
@ -0,0 +1,164 @@
|
||||
import math
|
||||
import json
|
||||
import discord
|
||||
|
||||
from gwendolyn.utils import cap
|
||||
|
||||
|
||||
class LookupFuncs():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.saves = ["strength_save","dexterity_save","constitution_save","intelligence_save","wisdom_save","charisma_save"]
|
||||
self.abilities = ["acrobatics","animal_handling","arcana","athletics","deception","history","insight","intimidation","investigation","medicine","nature","perception","performance","persuasion","religion","sleight_of_hand","stealth","survival"]
|
||||
|
||||
# Calculates D&D stat modifier
|
||||
def modifier(self, statistic):
|
||||
mods = math.floor((statistic-10)/2)
|
||||
if mods >= 0:
|
||||
mods = "+"+str(mods)
|
||||
return(str(mods))
|
||||
|
||||
# Looks up a monster
|
||||
async def monsterFunc(self, ctx, query):
|
||||
query = cap(query)
|
||||
self.bot.log("Looking up "+query)
|
||||
|
||||
# 1-letter monsters don't exist
|
||||
if len(query) < 2:
|
||||
self.bot.log("Monster name too short")
|
||||
await ctx.send("I don't know that monster...")
|
||||
else:
|
||||
# Opens "monsters.json"
|
||||
data = json.load(open('gwendolyn/resources/lookup/monsters.json', encoding = "utf8"))
|
||||
for monster in data:
|
||||
if "name" in monster and str(query) == monster["name"]:
|
||||
self.bot.log("Found it!")
|
||||
|
||||
# Looks at the information about the monster and returns that information
|
||||
# in separate variables, allowing Gwendolyn to know where to separate
|
||||
# the messages
|
||||
if monster["subtype"] != "":
|
||||
types = (monster["type"]+" ("+monster["subtype"]+")")
|
||||
else:
|
||||
types = monster["type"]
|
||||
con_mod = math.floor((monster["constitution"]-10)/2)
|
||||
hit_dice = monster["hit_dice"]
|
||||
|
||||
stats = ("**Str:** "+str(monster["strength"])+" ("+self.modifier(monster["strength"])+")\t**Dex:** "+str(monster["dexterity"])+" ("+self.modifier(monster["dexterity"])+")\t**Con:** "+str(monster["constitution"])+" ("+self.modifier(monster["constitution"])+")\n**Int: **"+str(monster["intelligence"])+" ("+self.modifier(monster["intelligence"])+")\t**Wis: **"+str(monster["wisdom"])+" ("+self.modifier(monster["wisdom"])+")\t**Cha: **"+str(monster["charisma"])+" ("+self.modifier(monster["charisma"])+")")
|
||||
|
||||
saving_throws = ""
|
||||
for save in self.saves:
|
||||
if save in monster:
|
||||
if monster[save] >= 0:
|
||||
saving_throws += " "+cap(save[:3])+" +"+str(monster[save])+","
|
||||
else:
|
||||
saving_throws += " "+cap(save[:3])+" "+str(monster[save])+","
|
||||
if saving_throws != "":
|
||||
saving_throws = "\n**Saving Throws**"+saving_throws[:-1]
|
||||
|
||||
skills = ""
|
||||
for skill in self.abilities:
|
||||
if skill in monster:
|
||||
if monster[skill] >= 0:
|
||||
skills += " "+cap(skill.replace("_"," "))+" +"+str(monster[skill])+","
|
||||
else:
|
||||
skills += " "+cap(skill.replace("_"," "))+" "+str(monster[skill])+","
|
||||
if skills != "":
|
||||
skills = "\n**Skills**"+skills[:-1]
|
||||
|
||||
vulnerabilities = monster["damage_vulnerabilities"]
|
||||
if vulnerabilities != "":
|
||||
vulnerabilities = "\n**Damage Vulnerabilities** "+vulnerabilities
|
||||
|
||||
resistances = monster["damage_resistances"]
|
||||
if resistances != "":
|
||||
resistances = "\n**Damage Resistances** "+resistances
|
||||
|
||||
immunities = monster["damage_immunities"]
|
||||
if immunities != "":
|
||||
immunities = "\n**Damage Immunities** "+immunities
|
||||
|
||||
c_immunities = monster["condition_immunities"]
|
||||
if c_immunities != "":
|
||||
c_immunities = "\n**Condition Immunities** "+c_immunities
|
||||
|
||||
specialAbilities = ""
|
||||
if "special_abilities" in monster:
|
||||
for ability in monster["special_abilities"]:
|
||||
specialAbilities += "\n\n***"+ability["name"]+".*** "+ability["desc"]
|
||||
|
||||
act = ""
|
||||
if "actions" in monster:
|
||||
for action in monster["actions"]:
|
||||
act += "\n\n***"+action["name"]+".*** "+action["desc"]
|
||||
|
||||
react = ""
|
||||
if "reactions" in monster:
|
||||
for reaction in monster["reactions"]:
|
||||
react += "\n\n***"+reaction["name"]+".*** "+reaction["desc"]
|
||||
|
||||
legendaryActions = ""
|
||||
if "legendary_actions" in monster:
|
||||
for action in monster["legendary_actions"]:
|
||||
legendaryActions += "\n\n***"+action["name"]+".*** "+action["desc"]
|
||||
|
||||
if con_mod < 0:
|
||||
hit_dice += (" - "+str(con_mod * int(monster["hit_dice"].replace("d"," ").split()[0])*(-1)))
|
||||
if con_mod > 0:
|
||||
hit_dice += (" + "+str(con_mod * int(monster["hit_dice"].replace("d"," ").split()[0])))
|
||||
|
||||
new_part = "\n--------------------"
|
||||
|
||||
monster_type = monster["size"]+" "+types+", "+monster["alignment"]+"*"
|
||||
|
||||
basic_info = "\n**Armor Class** "+str(monster["armor_class"])+"\n**Hit Points** "+str(monster["hit_points"])+" ("+hit_dice+")\n**Speed **"+monster["speed"]+new_part+"\n"
|
||||
|
||||
info = (monster_type+new_part+basic_info+stats+new_part+saving_throws+skills+vulnerabilities+resistances+immunities+c_immunities+"\n**Senses** "+monster["senses"]+"\n**Languages** "+monster["languages"]+"\n**Challenge** "+monster["challenge_rating"])
|
||||
|
||||
monsterInfo = [(info, query),
|
||||
(specialAbilities, "Special Abilities"),
|
||||
(act, "Actions"),
|
||||
(react, "Reactions"),
|
||||
(legendaryActions, "Legendary Actions")]
|
||||
|
||||
self.bot.log("Returning monster information")
|
||||
|
||||
# Sends the received information. Separates into separate messages if
|
||||
# there is too much text
|
||||
await ctx.send(f"Result for \"{query}\"")
|
||||
for text, title in monsterInfo:
|
||||
if text != "":
|
||||
if len(text) < 2000:
|
||||
em = discord.Embed(title = title, description = text, colour=0xDEADBF)
|
||||
await ctx.channel.send(embed = em)
|
||||
else:
|
||||
index = text[:2000].rfind(".")+1
|
||||
em1 = discord.Embed(title = title, description = text[:index], colour=0xDEADBF)
|
||||
await ctx.channel.send(embed = em1)
|
||||
em2 = discord.Embed(title = "", description = text[index+1:], colour=0xDEADBF)
|
||||
await ctx.channel.send(embed = em2)
|
||||
|
||||
break
|
||||
else:
|
||||
self.bot.log("Monster not in database")
|
||||
await ctx.send("I don't know that monster...")
|
||||
|
||||
# Looks up a spell
|
||||
async def spellFunc(self, ctx, query):
|
||||
query = cap(query)
|
||||
self.bot.log("Looking up "+query)
|
||||
|
||||
# Opens "spells.json"
|
||||
data = json.load(open('gwendolyn/resources/lookup/spells.json', encoding = "utf8"))
|
||||
if query in data:
|
||||
self.bot.log("Returning spell information")
|
||||
sendMessage = (f"***{query}***\n*{data[query]['level']} level {data[query]['school']}\nCasting Time: {data[query]['casting_time']}\nRange:{data[query]['range']}\nComponents:{data[query]['components']}\nDuration:{data[query]['duration']}*\n \n{data[query]['description']}")
|
||||
else:
|
||||
self.bot.log("I don't know that spell (error code 501)")
|
||||
sendMessage = "I don't think that's a spell (error code 501)"
|
||||
|
||||
if len(sendMessage) > 2000:
|
||||
await ctx.send(sendMessage[:2000])
|
||||
await ctx.send(sendMessage[2000:])
|
||||
else:
|
||||
await ctx.send(sendMessage)
|
5
gwendolyn/funcs/other/__init__.py
Normal file
5
gwendolyn/funcs/other/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Misc. functions for Gwendolyn."""
|
||||
|
||||
__all__ = ["Other"]
|
||||
|
||||
from .other import Other
|
413
gwendolyn/funcs/other/bedre_netflix.py
Normal file
413
gwendolyn/funcs/other/bedre_netflix.py
Normal file
@ -0,0 +1,413 @@
|
||||
import requests, imdb, discord, json, math, time, asyncio
|
||||
|
||||
class BedreNetflix():
|
||||
def __init__(self,bot):
|
||||
self.bot = bot
|
||||
ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]]
|
||||
|
||||
self.radarrURL = "http://"+ip+":7878/api/v3/"
|
||||
self.sonarrURL = "http://"+ip+":8989/api/"
|
||||
self.qbittorrentURL = "http://"+ip+":8080/api/v2/"
|
||||
self.moviePath = "/media/plex/Server/movies/"
|
||||
self.showPath = "/media/plex/Server/Shows/"
|
||||
|
||||
#Returns a list of no more than 5 options when user requests a movie
|
||||
async def requestMovie(self, ctx, movieName):
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
self.bot.log("Searching for "+movieName)
|
||||
movieList = imdb.IMDb().search_movie(movieName)
|
||||
movies = []
|
||||
for movie in movieList:
|
||||
if movie["kind"] == "movie":
|
||||
movies.append(movie)
|
||||
if len(movies) > 5:
|
||||
movies = movies[:5]
|
||||
|
||||
if len(movies) == 1:
|
||||
messageTitle = "**Is it this movie?**"
|
||||
else:
|
||||
messageTitle = "**Is it any of these movies?**"
|
||||
|
||||
messageText = ""
|
||||
imdb_ids = []
|
||||
|
||||
for x, movie in enumerate(movies):
|
||||
try:
|
||||
messageText += "\n"+str(x+1)+") "+movie["title"]+" ("+str(movie["year"])+")"
|
||||
except:
|
||||
try:
|
||||
messageText += "\n"+str(x+1)+") "+movie["title"]
|
||||
except:
|
||||
messageText += "Error"
|
||||
imdb_ids.append(movie.movieID)
|
||||
|
||||
self.bot.log("Returning a list of "+str(len(movies))+" possible movies: "+str(imdb_ids))
|
||||
|
||||
em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00)
|
||||
|
||||
message = await ctx.send(embed=em)
|
||||
|
||||
messageData = {"message_id":message.id,"imdb_ids":imdb_ids}
|
||||
|
||||
with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f:
|
||||
json.dump(messageData,f)
|
||||
|
||||
if len(movies) == 1:
|
||||
await message.add_reaction("✔️")
|
||||
else:
|
||||
for x in range(len(movies)):
|
||||
await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][x])
|
||||
|
||||
await message.add_reaction("❌")
|
||||
|
||||
message = await ctx.channel.fetch_message(message.id)
|
||||
if message.content != "" and not isinstance(ctx.channel, discord.DMChannel):
|
||||
await message.clear_reactions()
|
||||
|
||||
#Adds the requested movie to Bedre Netflix
|
||||
async def add_movie(self, message, imdbId, editMessage = True):
|
||||
if imdbId == None:
|
||||
self.bot.log("Did not find what the user was searching for")
|
||||
if editMessage:
|
||||
await message.edit(embed = None, content = "Try searching for the IMDB id")
|
||||
else:
|
||||
await message.channel.send("Try searching for the IMDB id")
|
||||
else:
|
||||
self.bot.log("Trying to add movie "+str(imdbId))
|
||||
apiKey = self.bot.credentials["radarr_key"]
|
||||
response = requests.get(self.radarrURL+"movie/lookup/imdb?imdbId=tt"+imdbId+"&apiKey="+apiKey)
|
||||
lookupData = response.json()
|
||||
postData = {"qualityProfileId": 1,
|
||||
"rootFolderPath" : self.moviePath,
|
||||
"monitored" : True,
|
||||
"addOptions": {"searchForMovie": True}}
|
||||
for key in ["tmdbId","title","titleSlug","images","year"]:
|
||||
postData.update({key : lookupData[key]})
|
||||
|
||||
r = requests.post(url= self.radarrURL+"movie?apikey="+apiKey,json = postData)
|
||||
|
||||
if r.status_code == 201:
|
||||
if editMessage:
|
||||
await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix")
|
||||
else:
|
||||
await message.channel.send(postData["title"]+" successfully added to Bedre Netflix")
|
||||
|
||||
self.bot.log("Added "+postData["title"]+" to Bedre Netflix")
|
||||
elif r.status_code == 400:
|
||||
text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available"
|
||||
if editMessage:
|
||||
await message.edit(embed = None, content = text)
|
||||
else:
|
||||
await message.channel.send(text)
|
||||
else:
|
||||
if editMessage:
|
||||
await message.edit(embed = None, content = "Something went wrong")
|
||||
else:
|
||||
await message.channel.send("Something went wrong")
|
||||
self.bot.log(str(r.status_code)+" "+r.reason)
|
||||
|
||||
#Returns a list of no more than 5 options when user requests a show
|
||||
async def requestShow(self, ctx, showName):
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
self.bot.log("Searching for "+showName)
|
||||
movies = imdb.IMDb().search_movie(showName) #Replace with tvdb
|
||||
shows = []
|
||||
for movie in movies:
|
||||
if movie["kind"] in ["tv series","tv miniseries"]:
|
||||
shows.append(movie)
|
||||
if len(shows) > 5:
|
||||
shows = shows[:5]
|
||||
|
||||
if len(shows) == 1:
|
||||
messageTitle = "**Is it this show?**"
|
||||
else:
|
||||
messageTitle = "**Is it any of these shows?**"
|
||||
|
||||
messageText = ""
|
||||
imdb_names = []
|
||||
|
||||
for x, show in enumerate(shows):
|
||||
try:
|
||||
messageText += "\n"+str(x+1)+") "+show["title"]+" ("+str(show["year"])+")"
|
||||
except:
|
||||
try:
|
||||
messageText += "\n"+str(x+1)+") "+show["title"]
|
||||
except:
|
||||
messageText += "Error"
|
||||
imdb_names.append(show["title"])
|
||||
|
||||
self.bot.log("Returning a list of "+str(len(shows))+" possible shows: "+str(imdb_names))
|
||||
|
||||
em = discord.Embed(title=messageTitle,description=messageText,colour=0x00FF00)
|
||||
|
||||
message = await ctx.send(embed=em)
|
||||
|
||||
messageData = {"message_id":message.id,"imdb_names":imdb_names}
|
||||
|
||||
with open("gwendolyn/resources/bedre_netflix/old_message"+str(ctx.channel.id),"w") as f:
|
||||
json.dump(messageData,f)
|
||||
|
||||
if len(shows) == 1:
|
||||
await message.add_reaction("✔️")
|
||||
else:
|
||||
for x in range(len(shows)):
|
||||
await message.add_reaction(["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣"][x])
|
||||
|
||||
await message.add_reaction("❌")
|
||||
|
||||
message = await ctx.channel.fetch_message(message.id)
|
||||
if message.content != "" and not isinstance(ctx.channel, discord.DMChannel):
|
||||
await message.clear_reactions()
|
||||
|
||||
#Adds the requested show to Bedre Netflix
|
||||
async def add_show(self, message, imdb_name):
|
||||
if imdb_name == None:
|
||||
self.bot.log("Did not find what the user was searching for")
|
||||
await message.edit(embed = None, content = "Try searching for the IMDB id")
|
||||
else:
|
||||
self.bot.log("Trying to add show "+str(imdb_name))
|
||||
apiKey = self.bot.credentials["sonarr_key"]
|
||||
response = requests.get(self.sonarrURL+"series/lookup?term="+imdb_name.replace(" ","%20")+"&apiKey="+apiKey)
|
||||
lookupData = response.json()[0]
|
||||
postData = {"ProfileId" : 1,
|
||||
"rootFolderPath" : self.showPath,
|
||||
"monitored" : True,
|
||||
"addOptions" : {"searchForMissingEpisodes" : True}}
|
||||
for key in ["tvdbId","title","titleSlug","images","seasons"]:
|
||||
postData.update({key : lookupData[key]})
|
||||
|
||||
r = requests.post(url= self.sonarrURL+"series?apikey="+apiKey,json = postData)
|
||||
|
||||
if r.status_code == 201:
|
||||
await message.edit(embed = None, content = postData["title"]+" successfully added to Bedre Netflix")
|
||||
self.bot.log("Added a "+postData["title"]+" to Bedre Netflix")
|
||||
elif r.status_code == 400:
|
||||
text = f"{postData['title']} is either already on Bedre Netflix, downloading, or not available"
|
||||
await message.edit(embed = None, content = text)
|
||||
else:
|
||||
await message.edit(embed = None, content = "Something went wrong")
|
||||
self.bot.log(str(r.status_code)+" "+r.reason)
|
||||
|
||||
#Generates a list of all torrents and returns formatted list and whether all torrents are downloaded
|
||||
async def genDownloadList(self, showDM, showMovies, showShows, episodes):
|
||||
self.bot.log("Generating torrent list")
|
||||
titleWidth = 100
|
||||
message = []
|
||||
allDownloaded = True
|
||||
|
||||
if showDM:
|
||||
message.append("")
|
||||
DMSectionTitle = "*Torrent Downloads*"
|
||||
DMSectionTitleLine = "-"*((titleWidth-len(DMSectionTitle))//2)
|
||||
message.append(DMSectionTitleLine+DMSectionTitle+DMSectionTitleLine)
|
||||
response = requests.get(self.qbittorrentURL+"torrents/info")
|
||||
torrentList = response.json()
|
||||
|
||||
if len(torrentList) > 0:
|
||||
for torrent in torrentList:
|
||||
torrentName = torrent["name"]
|
||||
if len(torrentName) > 30:
|
||||
if torrentName[26] == " ":
|
||||
torrentName = torrentName[:26]+"...."
|
||||
else:
|
||||
torrentName = torrentName[:27]+"..."
|
||||
while len(torrentName) < 30:
|
||||
torrentName += " "
|
||||
|
||||
if torrent["size"] == 0:
|
||||
downloadedRatio = 0
|
||||
elif torrent["amount_left"] == 0:
|
||||
downloadedRatio = 1
|
||||
else:
|
||||
downloadedRatio = min(torrent["downloaded"]/torrent["size"],1)
|
||||
progressBar = "|"+("█"*math.floor(downloadedRatio*20))
|
||||
while len(progressBar) < 21:
|
||||
progressBar += " "
|
||||
|
||||
progressBar += "| "+str(math.floor(downloadedRatio*100))+"%"
|
||||
|
||||
while len(progressBar) < 27:
|
||||
progressBar += " "
|
||||
|
||||
etaInSeconds = torrent["eta"]
|
||||
|
||||
if etaInSeconds >= 8640000:
|
||||
eta = "∞"
|
||||
else:
|
||||
eta = ""
|
||||
if etaInSeconds >= 86400:
|
||||
eta += str(math.floor(etaInSeconds/86400))+"d "
|
||||
if etaInSeconds >= 3600:
|
||||
eta += str(math.floor((etaInSeconds%86400)/3600))+"h "
|
||||
if etaInSeconds >= 60:
|
||||
eta += str(math.floor((etaInSeconds%3600)/60))+"m "
|
||||
|
||||
eta += str(etaInSeconds%60)+"s"
|
||||
|
||||
torrentInfo = torrentName+" "+progressBar+" (Eta: "+eta+")"
|
||||
|
||||
if torrent["state"] == "stalledDL":
|
||||
torrentInfo += " (Stalled)"
|
||||
|
||||
if not (downloadedRatio == 1 and torrent["last_activity"] < time.time()-7200):
|
||||
message.append(torrentInfo)
|
||||
|
||||
if downloadedRatio < 1 and torrent["state"] != "stalledDL":
|
||||
allDownloaded = False
|
||||
else:
|
||||
message.append("No torrents currently downloading")
|
||||
|
||||
if showMovies:
|
||||
message.append("")
|
||||
movieSectionTitle = "*Missing movies not downloading*"
|
||||
movieSectionTitleLine = "-"*((titleWidth-len(movieSectionTitle))//2)
|
||||
message.append(movieSectionTitleLine+movieSectionTitle+movieSectionTitleLine)
|
||||
movieList = requests.get(self.radarrURL+"movie?apiKey="+self.bot.credentials["radarr_key"]).json()
|
||||
movieQueue = requests.get(self.radarrURL+"queue?apiKey="+self.bot.credentials["radarr_key"]).json()
|
||||
movieQueueIDs = []
|
||||
|
||||
for queueItem in movieQueue["records"]:
|
||||
movieQueueIDs.append(queueItem["movieId"])
|
||||
|
||||
for movie in movieList:
|
||||
if not movie["hasFile"]:
|
||||
if movie["id"] not in movieQueueIDs:
|
||||
movieName = movie["title"]
|
||||
if len(movieName) > 40:
|
||||
if movieName[36] == " ":
|
||||
movieName = movieName[:36]+"...."
|
||||
else:
|
||||
movieName = movieName[:37]+"..."
|
||||
|
||||
while len(movieName) < 41:
|
||||
movieName += " "
|
||||
|
||||
if movie["monitored"]:
|
||||
movieInfo = movieName+"Could not find a torrent"
|
||||
else:
|
||||
movieInfo = movieName+"No torrent exists. Likely because the movie is not yet released on DVD"
|
||||
|
||||
message.append(movieInfo)
|
||||
|
||||
if showShows:
|
||||
message.append("")
|
||||
showSectionTitle = "*Missing shows not downloading*"
|
||||
showSectionTitleLine = "-"*((titleWidth-len(showSectionTitle))//2)
|
||||
message.append(showSectionTitleLine+showSectionTitle+showSectionTitleLine)
|
||||
|
||||
showList = requests.get(self.sonarrURL+"series?apiKey="+self.bot.credentials["sonarr_key"]).json()
|
||||
|
||||
for show in showList:
|
||||
if show["seasons"][0]["seasonNumber"] == 0:
|
||||
seasons = show["seasons"][1:]
|
||||
else:
|
||||
seasons = show["seasons"]
|
||||
if any(i["statistics"]["episodeCount"] != i["statistics"]["totalEpisodeCount"] for i in seasons):
|
||||
if all(i["statistics"]["episodeCount"] == 0 for i in seasons):
|
||||
message.append(show["title"] + " (all episodes)")
|
||||
else:
|
||||
if episodes:
|
||||
missingEpisodes = sum(i["statistics"]["totalEpisodeCount"] - i["statistics"]["episodeCount"] for i in seasons)
|
||||
message.append(show["title"] + f" ({missingEpisodes} episodes)")
|
||||
|
||||
message.append("-"*titleWidth)
|
||||
|
||||
messageText = "```"+"\n".join(message[1:])+"```"
|
||||
if messageText == "``````":
|
||||
messageText = "There are no torrents downloading right. If the torrent you're looking for was added more than 24 hours ago, it might already be on Bedre Netflix."
|
||||
return messageText, allDownloaded
|
||||
|
||||
async def downloading(self, ctx, content):
|
||||
async def SendLongMessage(ctx,messageText):
|
||||
if len(messageText) <= 1994:
|
||||
await ctx.send("```"+messageText+"```")
|
||||
else:
|
||||
cutOffIndex = messageText[:1994].rfind("\n")
|
||||
await ctx.send("```"+messageText[:cutOffIndex]+"```")
|
||||
await SendLongMessage(ctx,messageText[cutOffIndex+1:])
|
||||
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
# showDM, showMovies, showShows, episodes
|
||||
params = [False, False, False, False]
|
||||
showDMArgs = ["d", "dm", "downloading", "downloadmanager"]
|
||||
showMoviesArgs = ["m", "movies"]
|
||||
showShowsArgs = ["s", "shows", "series"]
|
||||
episodesArgs = ["e", "episodes"]
|
||||
argList = [showDMArgs, showMoviesArgs, showShowsArgs, episodesArgs]
|
||||
inputArgs = []
|
||||
validArguments = True
|
||||
|
||||
while content != "" and validArguments:
|
||||
if content[0] == " ":
|
||||
content = content[1:]
|
||||
elif content[0] == "-":
|
||||
if content[1] == "-":
|
||||
argStart = 2
|
||||
if " " in content:
|
||||
argStop = content.find(" ")
|
||||
else:
|
||||
argStop = None
|
||||
else:
|
||||
argStart = 1
|
||||
argStop = 2
|
||||
|
||||
inputArgs.append(content[argStart:argStop])
|
||||
if argStop is None:
|
||||
content = ""
|
||||
else:
|
||||
content = content[argStop:]
|
||||
else:
|
||||
validArguments = False
|
||||
|
||||
if validArguments:
|
||||
for x, argAliases in enumerate(argList):
|
||||
argInInput = [i in inputArgs for i in argAliases]
|
||||
if any(argInInput):
|
||||
inputArgs.remove(argAliases[argInInput.index(True)])
|
||||
params[x] = True
|
||||
|
||||
if len(inputArgs) != 0 or (params[2] == False and params[3] == True):
|
||||
validArguments = False
|
||||
|
||||
showAnything = any(i for i in params)
|
||||
if validArguments and showAnything:
|
||||
messageText, allDownloaded = await self.genDownloadList(*params)
|
||||
if messageText.startswith("```"):
|
||||
|
||||
if len(messageText) <= 2000:
|
||||
if not allDownloaded:
|
||||
updatesLeft = 60
|
||||
messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```"
|
||||
old_message = await ctx.send(messageText)
|
||||
|
||||
while ((not allDownloaded) and updatesLeft > 0):
|
||||
await asyncio.sleep(10)
|
||||
updatesLeft -= 1
|
||||
messageText, allDownloaded = await self.genDownloadList(*params)
|
||||
messageText = messageText[:-3]+"\nThis message will update every 10 seconds for "+str(math.ceil(updatesLeft/6))+" more minutes\n```"
|
||||
await old_message.edit(content = messageText)
|
||||
|
||||
messageText, allDownloaded = await self.genDownloadList(*params)
|
||||
|
||||
if messageText.startswith("```"):
|
||||
if allDownloaded:
|
||||
self.bot.log("All torrents are downloaded")
|
||||
else:
|
||||
messageText = messageText[:-3]+"\nThis message will not update anymore\n```"
|
||||
self.bot.log("The message updated 20 times")
|
||||
|
||||
await old_message.edit(content = messageText)
|
||||
|
||||
else:
|
||||
await ctx.send(messageText)
|
||||
else:
|
||||
messageText = messageText[3:-3]
|
||||
await SendLongMessage(ctx,messageText)
|
||||
else:
|
||||
await ctx.send(messageText)
|
||||
else:
|
||||
await ctx.send("Invalid or repeated parameters. Use '/help downloading' to see valid parameters.")
|
||||
|
93
gwendolyn/funcs/other/generators.py
Normal file
93
gwendolyn/funcs/other/generators.py
Normal file
@ -0,0 +1,93 @@
|
||||
import random
|
||||
|
||||
class Generators():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
# Returns a list of all letter pairs in the text
|
||||
def make_pairs(self, corpus):
|
||||
for i in range(len(corpus)-1):
|
||||
yield (corpus[i], corpus[i+1])
|
||||
|
||||
# Returns a list of all letter triplets in the text
|
||||
def make_triplets(self, corpus):
|
||||
for i in range(len(corpus)-2):
|
||||
yield (corpus[i], corpus[i+1], corpus[i+2])
|
||||
|
||||
# Generates a random name
|
||||
async def nameGen(self, ctx):
|
||||
# Makes a list of all names from "names.txt"
|
||||
names = open('gwendolyn/resources/names.txt', encoding='utf8').read()
|
||||
corpus = list(names)
|
||||
|
||||
# Makes a list of pairs
|
||||
pairs = self.make_pairs(corpus)
|
||||
triplets = self.make_triplets(corpus)
|
||||
|
||||
letter_dict = {}
|
||||
|
||||
# Makes a dictionary of all letters that come after all other letters
|
||||
for letter_1, letter_2 in pairs:
|
||||
if letter_1 in letter_dict.keys():
|
||||
letter_dict[letter_1].append(letter_2)
|
||||
else:
|
||||
letter_dict[letter_1] = [letter_2]
|
||||
|
||||
for letter_1, letter_2, letter_3 in triplets:
|
||||
if letter_1+letter_2 in letter_dict.keys():
|
||||
letter_dict[letter_1+letter_2].append(letter_3)
|
||||
else:
|
||||
letter_dict[letter_1+letter_2] = [letter_3]
|
||||
|
||||
# Choses a random first letter
|
||||
first_letter = random.choice(corpus)
|
||||
|
||||
# Makes sure the first letter is not something a name can't start with.
|
||||
while first_letter.islower() or first_letter == " " or first_letter == "-" or first_letter == "\n":
|
||||
first_letter = random.choice(corpus)
|
||||
|
||||
# Starts the name
|
||||
chain = [first_letter]
|
||||
|
||||
# Picks second letter
|
||||
second_letter = random.choice(letter_dict[chain[-1]])
|
||||
|
||||
while second_letter == "\n":
|
||||
second_letter = random.choice(letter_dict[chain[-1]])
|
||||
|
||||
chain.append(second_letter)
|
||||
|
||||
done = False
|
||||
|
||||
# Creates the name one letter at a time
|
||||
while done == False:
|
||||
if random.randint(1,10) > 1:
|
||||
try:
|
||||
new_letter = random.choice(letter_dict[chain[-2]+chain[-1]])
|
||||
except KeyError():
|
||||
new_letter = random.choice(letter_dict[chain[-1]])
|
||||
else:
|
||||
new_letter = random.choice(letter_dict[chain[-1]])
|
||||
chain.append(new_letter)
|
||||
# Ends name if the name ends
|
||||
if new_letter == "\n":
|
||||
done = True
|
||||
genName = "".join(chain)
|
||||
self.bot.log("Generated "+genName[:-1])
|
||||
|
||||
# Returns the name
|
||||
await ctx.send(genName)
|
||||
|
||||
# Generates a random tavern name
|
||||
async def tavernGen(self, ctx):
|
||||
# Lists first parts, second parts and third parts of tavern names
|
||||
fp = ["The Silver","The Golden","The Staggering","The Laughing","The Prancing","The Gilded","The Running","The Howling","The Slaughtered","The Leering","The Drunken","The Leaping","The Roaring","The Frowning","The Lonely","The Wandering","The Mysterious","The Barking","The Black","The Gleaming","The Tap-Dancing","The Sad","The Sexy","The Artificial","The Groovy","The Merciful","The Confused","The Pouting","The Horny","The Okay","The Friendly","The Hungry","The Handicapped","The Fire-breathing","The One-Eyed","The Psychotic","The Mad","The Evil","The Idiotic","The Trusty","The Busty"]
|
||||
sp = ["Eel","Dolphin","Dwarf","Pegasus","Pony","Rose","Stag","Wolf","Lamb","Demon","Goat","Spirit","Horde","Jester","Mountain","Eagle","Satyr","Dog","Spider","Star","Dad","Rat","Jeremy","Mouse","Unicorn","Pearl","Ant","Crab","Penguin","Octopus","Lawyer","Ghost","Toad","Handjob","Immigrant","SJW","Dragon","Bard","Sphinx","Soldier","Salmon","Owlbear","Kite","Frost Giant","Arsonist"]
|
||||
tp = [" Tavern"," Inn","","","","","","","","",""]
|
||||
|
||||
# Picks one of each
|
||||
genTav = random.choice(fp)+" "+random.choice(sp)+random.choice(tp)
|
||||
self.bot.log("Generated "+genTav)
|
||||
|
||||
# Return the name
|
||||
await ctx.send(genTav)
|
81
gwendolyn/funcs/other/nerd_shit.py
Normal file
81
gwendolyn/funcs/other/nerd_shit.py
Normal file
@ -0,0 +1,81 @@
|
||||
import discord, discord_slash, wolframalpha, requests, os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
class NerdShit():
|
||||
def __init__(self, bot):
|
||||
"""Runs misc commands."""
|
||||
self.bot = bot
|
||||
|
||||
async def wolfSearch(self,ctx,content):
|
||||
await self.bot.defer(ctx)
|
||||
fnt = ImageFont.truetype('gwendolyn/resources/fonts/times-new-roman.ttf', 20)
|
||||
self.bot.log("Requesting data")
|
||||
bot = wolframalpha.Client(self.bot.credentials["wolfram_alpha_key"])
|
||||
res = bot.query(content)
|
||||
|
||||
self.bot.log("Processing data")
|
||||
titles = []
|
||||
pods = []
|
||||
if int(res.numpods) > 0:
|
||||
for pod in res.pods:
|
||||
titles += [pod.title]
|
||||
for x, sub in enumerate(pod.subpods):
|
||||
pods += [sub]
|
||||
if x > 0:
|
||||
titles += [""]
|
||||
|
||||
podChunks = [pods[x:x+2] for x in range(0, len(pods), 2)]
|
||||
titleChucks = [titles[x:x+2] for x in range(0, len(titles), 2)]
|
||||
await ctx.send(f"Response for \"{content}\"")
|
||||
|
||||
for x, chunk in enumerate(podChunks):
|
||||
width = 0
|
||||
for title in titleChucks[x]:
|
||||
width = max(width,fnt.getsize(title)[0])
|
||||
height = 5
|
||||
heights = []
|
||||
for count, pod in enumerate(chunk):
|
||||
heights += [height]
|
||||
width = max(width,int(pod.img['@width']))
|
||||
if titleChucks[x][count] == "":
|
||||
placeForText = 0
|
||||
else:
|
||||
placeForText = 30
|
||||
height += int(pod.img["@height"]) + 10 + placeForText
|
||||
|
||||
width += 10
|
||||
height += 5
|
||||
wolfImage = Image.new("RGB",(width,height),color=(255,255,255))
|
||||
|
||||
for count, pod in enumerate(chunk):
|
||||
response = requests.get(pod.img["@src"])
|
||||
file = open("gwendolyn/resources/wolfTemp.png", "wb")
|
||||
file.write(response.content)
|
||||
file.close()
|
||||
old_image = Image.open("gwendolyn/resources/wolfTemp.png")
|
||||
oldSize = old_image.size
|
||||
if titleChucks[x][count] == "":
|
||||
placeForText = 0
|
||||
else:
|
||||
placeForText = 30
|
||||
newSize = (width,int(oldSize[1]+10+placeForText))
|
||||
newImage = Image.new("RGB",newSize,color=(255,255,255))
|
||||
newImage.paste(old_image, (int((int(oldSize[0]+10)-oldSize[0])/2),int(((newSize[1]-placeForText)-oldSize[1])/2)+placeForText))
|
||||
if titleChucks[x][count] != "":
|
||||
d = ImageDraw.Draw(newImage,"RGB")
|
||||
d.text((5,7),titleChucks[x][count],font=fnt,fill=(150,150,150))
|
||||
|
||||
wolfImage.paste(newImage,(0,heights[count]))
|
||||
newImage.close()
|
||||
old_image.close()
|
||||
count += 1
|
||||
|
||||
wolfImage.save("gwendolyn/resources/wolf.png")
|
||||
wolfImage.close()
|
||||
await ctx.channel.send(file = discord.File("gwendolyn/resources/wolf.png"))
|
||||
|
||||
os.remove("gwendolyn/resources/wolf.png")
|
||||
os.remove("gwendolyn/resources/wolfTemp.png")
|
||||
else:
|
||||
self.bot.log("No returned data")
|
||||
await ctx.send("Could not find anything relating to your search")
|
196
gwendolyn/funcs/other/other.py
Normal file
196
gwendolyn/funcs/other/other.py
Normal file
@ -0,0 +1,196 @@
|
||||
import imdb # Used in movieFunc
|
||||
import random # Used in movieFunc
|
||||
import discord # Used in movieFunc
|
||||
import datetime # Used in helloFunc
|
||||
import urllib # Used in imageFunc
|
||||
import lxml # Used in imageFunc
|
||||
import fandom # Used in findWikiPage
|
||||
import d20 # Used in rollDice
|
||||
import ast
|
||||
from .bedre_netflix import BedreNetflix
|
||||
from .nerd_shit import NerdShit
|
||||
from .generators import Generators
|
||||
|
||||
from gwendolyn.utils import cap
|
||||
|
||||
fandom.set_lang("da")
|
||||
fandom.set_wiki("senkulpa")
|
||||
|
||||
class MyStringifier(d20.MarkdownStringifier):
|
||||
def _str_expression(self, node):
|
||||
if node.comment == None:
|
||||
resultText = "Result"
|
||||
else:
|
||||
resultText = node.comment.capitalize()
|
||||
|
||||
return f"**{resultText}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
|
||||
|
||||
class Other():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.bedre_netflix = BedreNetflix(self.bot)
|
||||
self.nerd_shit = NerdShit(self.bot)
|
||||
self.generators = Generators(self.bot)
|
||||
|
||||
# Picks a random movie and returns information about it
|
||||
async def movieFunc(self, ctx):
|
||||
await self.bot.defer(ctx)
|
||||
|
||||
self.bot.log("Creating IMDb object")
|
||||
imdbClient = imdb.IMDb()
|
||||
|
||||
self.bot.log("Picking a movie")
|
||||
with open("gwendolyn/resources/movies.txt", "r") as f:
|
||||
movieList = f.read().split("\n")
|
||||
movieName = random.choice(movieList)
|
||||
|
||||
self.bot.log(f"Searching for {movieName}")
|
||||
searchResult = imdbClient.search_movie(movieName)
|
||||
|
||||
self.bot.log("Getting the data")
|
||||
movie = searchResult[0]
|
||||
imdbClient.update(movie)
|
||||
|
||||
self.bot.log("Successfully ran /movie")
|
||||
|
||||
title = movie["title"]
|
||||
plot = movie['plot'][0].split("::")[0]
|
||||
cover = movie['cover url'].replace("150","600").replace("101","404")
|
||||
cast = ", ".join([i["name"] for i in movie['cast'][:5]])
|
||||
embed = discord.Embed(title=title, description=plot, color=0x24ec19)
|
||||
embed.set_thumbnail(url=cover)
|
||||
embed.add_field(name="Cast", value=cast,inline = True)
|
||||
await ctx.send(embed = embed)
|
||||
|
||||
# Responds with a greeting of a time-appropriate maner
|
||||
async def helloFunc(self, ctx):
|
||||
def time_in_range(start, end, x):
|
||||
# Return true if x is in the range [start, end]
|
||||
if start <= end:
|
||||
return start <= x <= end
|
||||
else:
|
||||
return start <= x or x <= end
|
||||
|
||||
author = ctx.author.display_name
|
||||
now = datetime.datetime.now()
|
||||
if time_in_range(now.replace(hour=5, minute=0, second=0, microsecond=0),now.replace(hour=10, minute=0, second=0, microsecond=0), now):
|
||||
sendMessage = "Good morning, "+str(author)
|
||||
elif time_in_range(now.replace(hour=13, minute=0, second=0, microsecond=0),now.replace(hour=18, minute=0, second=0, microsecond=0), now):
|
||||
sendMessage = "Good afternoon, "+str(author)
|
||||
elif time_in_range(now.replace(hour=18, minute=0, second=0, microsecond=0),now.replace(hour=22, minute=0, second=0, microsecond=0), now):
|
||||
sendMessage = "Good evening, "+str(author)
|
||||
elif time_in_range(now.replace(hour=22, minute=0, second=0, microsecond=0),now.replace(hour=23, minute=59, second=59, microsecond=0), now):
|
||||
sendMessage = "Good night, "+str(author)
|
||||
else:
|
||||
sendMessage = "Hello, "+str(author)
|
||||
|
||||
await ctx.send(sendMessage)
|
||||
|
||||
# Finds a random picture online
|
||||
async def imageFunc(self, ctx):
|
||||
# Picks a type of camera, which decides the naming scheme
|
||||
cams = ("one","two","three","four")
|
||||
cam = random.choice(cams)
|
||||
self.bot.log("Chose cam type "+cam)
|
||||
if cam == "one":
|
||||
a = str(random.randint(0 ,9))
|
||||
b = str(random.randint(0,9))
|
||||
c = str(random.randint(0,9))
|
||||
d = str(random.randint(0,9))
|
||||
search = ("img_"+a+b+c+d)
|
||||
elif cam == "two":
|
||||
a = str(random.randint(2012,2016))
|
||||
b = str(random.randint(1,12)).zfill(2)
|
||||
c = str(random.randint(1,29)).zfill(2)
|
||||
search = ("IMG_"+a+b+c)
|
||||
elif cam == "three":
|
||||
a = str(random.randint(1,500)).zfill(4)
|
||||
search = ("IMAG_"+a)
|
||||
elif cam == "four":
|
||||
a = str(random.randint(0,9))
|
||||
b = str(random.randint(0,9))
|
||||
c = str(random.randint(0,9))
|
||||
d = str(random.randint(0,9))
|
||||
search = ("DSC_"+a+b+c+d)
|
||||
|
||||
self.bot.log("Searching for "+search)
|
||||
|
||||
# Searches for the image and reads the resulting web page
|
||||
page = urllib.request.urlopen("https://www.bing.com/images/search?q="+search+"&safesearch=off")
|
||||
read = page.read()
|
||||
tree = lxml.etree.HTML(read)
|
||||
images = tree.xpath('//a[@class = "iusc"]/@m')
|
||||
|
||||
if len(images) == 0:
|
||||
await ctx.send("Found no images")
|
||||
else:
|
||||
# Picks an image
|
||||
number = random.randint(1,len(images))-1
|
||||
image = ast.literal_eval(str(images[number]))
|
||||
imageUrl = image["murl"]
|
||||
|
||||
self.bot.log("Picked image number "+str(number))
|
||||
|
||||
# Returns the image
|
||||
self.bot.log("Successfully returned an image")
|
||||
|
||||
await ctx.send(imageUrl)
|
||||
|
||||
# Finds a page from the Senkulpa Wikia
|
||||
async def findWikiPage(self, ctx, search : str):
|
||||
await self.bot.defer(ctx)
|
||||
foundPage = False
|
||||
|
||||
if search != "":
|
||||
self.bot.log("Trying to find wiki page for "+search)
|
||||
searchResults = fandom.search(search)
|
||||
if len(searchResults) > 0:
|
||||
foundPage = True
|
||||
searchResult = searchResults[0]
|
||||
else:
|
||||
self.bot.log("Couldn't find the page")
|
||||
await ctx.send("Couldn't find page (error code 1002)")
|
||||
else:
|
||||
foundPage = True
|
||||
self.bot.log("Searching for a random page")
|
||||
searchResult = fandom.random()
|
||||
|
||||
if foundPage:
|
||||
self.bot.log(f"Found page \"{searchResult[0]}\"")
|
||||
page = fandom.page(pageid = searchResult[1])
|
||||
content = page.summary
|
||||
|
||||
images = page.images
|
||||
if len(images) > 0:
|
||||
image = images[0]
|
||||
else:
|
||||
image = ""
|
||||
self.bot.log("Sending the embedded message",str(ctx.channel_id))
|
||||
content += f"\n[Læs mere]({page.url})"
|
||||
embed = discord.Embed(title = page.title, description = content, colour=0xDEADBF)
|
||||
if image != "":
|
||||
embed.set_thumbnail(url=image)
|
||||
|
||||
await ctx.send(embed = embed)
|
||||
|
||||
async def rollDice(self, ctx, rollString):
|
||||
user = ctx.author.display_name
|
||||
while len(rollString) > 1 and rollString[0] == " ":
|
||||
rollString = rollString[1:]
|
||||
|
||||
roll = d20.roll(rollString, allow_comments=True, stringifier=MyStringifier())
|
||||
await ctx.send(f"{user} :game_die:\n{roll}")
|
||||
|
||||
async def helpFunc(self, ctx, command):
|
||||
if command == "":
|
||||
with open("gwendolyn/resources/help/help.txt",encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
em = discord.Embed(title = "Help", description = text,colour = 0x59f442)
|
||||
await ctx.send(embed = em)
|
||||
else:
|
||||
self.bot.log(f"Looking for help-{command}.txt",str(ctx.channel_id))
|
||||
with open(f"gwendolyn/resources/help/help-{command}.txt",encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
em = discord.Embed(title = command.capitalize(), description = text,colour = 0x59f442)
|
||||
await ctx.send(embed = em)
|
||||
|
5
gwendolyn/funcs/star_wars_funcs/__init__.py
Normal file
5
gwendolyn/funcs/star_wars_funcs/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Functions related to the Star Wars TTRPG."""
|
||||
|
||||
__all__ = ["StarWars"]
|
||||
|
||||
from .star_wars import StarWars
|
10
gwendolyn/funcs/star_wars_funcs/star_wars.py
Normal file
10
gwendolyn/funcs/star_wars_funcs/star_wars.py
Normal file
@ -0,0 +1,10 @@
|
||||
from .star_wars_char import StarWarsChar
|
||||
from .star_wars_roll import StarWarsRoll
|
||||
from .star_wars_destiny import StarWarsDestiny
|
||||
|
||||
class StarWars():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.character = StarWarsChar(self.bot)
|
||||
self.roll = StarWarsRoll(self.bot)
|
||||
self.destiny = StarWarsDestiny(self.bot)
|
529
gwendolyn/funcs/star_wars_funcs/star_wars_char.py
Normal file
529
gwendolyn/funcs/star_wars_funcs/star_wars_char.py
Normal file
@ -0,0 +1,529 @@
|
||||
import json
|
||||
import string
|
||||
import discord
|
||||
|
||||
class StarWarsChar():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
def getCharName(self, user : str):
|
||||
self.bot.log("Getting name for "+self.bot.database_funcs.get_name(user)+"'s character")
|
||||
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
|
||||
|
||||
if userCharacter != None:
|
||||
self.bot.log("Name is "+userCharacter["Name"])
|
||||
return userCharacter["Name"]
|
||||
else:
|
||||
self.bot.log("Just using "+self.bot.database_funcs.get_name(user))
|
||||
return self.bot.database_funcs.get_name(user)
|
||||
|
||||
def setUpDict(self, cmd : dict):
|
||||
self.bot.log("Setting up a dictionary in a nice way")
|
||||
if bool(cmd):
|
||||
keys = list(cmd)
|
||||
values = list(cmd.values())
|
||||
result = ""
|
||||
if isinstance(values[0],dict):
|
||||
return ", ".join(values)
|
||||
else:
|
||||
for x, key in enumerate(keys):
|
||||
if type(key) is list:
|
||||
if x%3 != 2:
|
||||
result += "**" + key + "**" + ": " + ", ".join(values[x]) + " "
|
||||
else:
|
||||
result += "**" + key + "**" + ": " + ", ".join(values[x]) + "\n"
|
||||
else:
|
||||
if x%3 != 2:
|
||||
result += "**" + key + "**" + ": " + str(values[x]) + " "
|
||||
else:
|
||||
result += "**" + key + "**" + ": " + str(values[x]) + "\n"
|
||||
self.bot.log("Returning a dictionary, but well formatted")
|
||||
return result
|
||||
else:
|
||||
self.bot.log("Couldn't find anything")
|
||||
return "There doesn't seem to be anything here..."
|
||||
|
||||
def lookUp(self, data : dict, key : str, cmd : str = ""):
|
||||
if cmd == " ":
|
||||
cmd = ""
|
||||
elif cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
|
||||
self.bot.log("Looking up "+key)
|
||||
if key in data:
|
||||
self.bot.log(key+" exists")
|
||||
if cmd == "":
|
||||
if type(data[key]) is dict and key != "Weapons":
|
||||
return self.setUpDict(data[key])
|
||||
elif key == "Weapons":
|
||||
self.bot.log("Does this even get used? I'm too scared to delete it")
|
||||
if bool(data[key]):
|
||||
self.bot.log("Found "+(", ".join(list(data[key]))))
|
||||
return ", ".join(list(data[key]))
|
||||
else:
|
||||
self.bot.log("There is nothing here")
|
||||
return "There doesn't seem to be anything here..."
|
||||
else:
|
||||
if str(data[key]) != "":
|
||||
self.bot.log("Returning "+str(data[key]))
|
||||
return data[key]
|
||||
else:
|
||||
self.bot.log("There was nothing there")
|
||||
return "There doesn't seem to be anything here"
|
||||
elif cmd[0] == '+':
|
||||
self.bot.log("Trying to add to "+key)
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("Yeah, that fucked up")
|
||||
return "Can't do that"
|
||||
|
||||
if type(data[key]) is int:
|
||||
try:
|
||||
newValue = data[key] + int(cmd)
|
||||
data[key] = newValue
|
||||
self.bot.log("Added "+cmd+" to "+key)
|
||||
return data
|
||||
except:
|
||||
self.bot.log("Couldn't add "+cmd+" to "+key)
|
||||
return "Can't add that"
|
||||
elif type(data[key]) is list:
|
||||
try:
|
||||
data[key].append(cmd)
|
||||
self.bot.log("Added "+cmd+" to "+key)
|
||||
return data
|
||||
except:
|
||||
self.bot.log("Couldn't add "+cmd+" to "+key)
|
||||
return "Can't add that"
|
||||
else:
|
||||
self.bot.log("Yeah, I can't add that to "+key)
|
||||
return "Can't add that"
|
||||
|
||||
elif cmd[0] == '-':
|
||||
self.bot.log("Trying to remove/subtract from "+key)
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("Yeah, that fucked up")
|
||||
return "Can't do that"
|
||||
|
||||
if type(data[key]) is int:
|
||||
try:
|
||||
newValue = data[key] - int(cmd)
|
||||
data[key] = newValue
|
||||
self.bot.log("Subtracted "+cmd+" from "+key)
|
||||
return data
|
||||
except:
|
||||
self.bot.log("Couldn't subtract "+cmd+" from "+key)
|
||||
return "Can't remove that"
|
||||
elif type(data[key]) is list:
|
||||
try:
|
||||
data[key].remove(cmd)
|
||||
self.bot.log("Removed "+cmd+" from "+key)
|
||||
return data
|
||||
except:
|
||||
self.bot.log("Couldn't remove "+cmd+" from "+key)
|
||||
return "Can't remove that"
|
||||
else:
|
||||
self.bot.log("Yeah, I can't remove/subtract that from "+key)
|
||||
return "Can't remove that"
|
||||
else:
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
|
||||
if type(data[key]) is dict:
|
||||
newKey = cmd.split(" ")[0]
|
||||
cmd = cmd[len(newKey):]
|
||||
if cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
self.bot.log("Looking up "+newKey+" in "+key)
|
||||
lookUpResult = self.lookUp(data[key],newKey,cmd)
|
||||
if type(lookUpResult) is dict:
|
||||
data[key] = lookUpResult
|
||||
return data
|
||||
else:
|
||||
return lookUpResult
|
||||
elif type(data[key]) != list:
|
||||
self.bot.log("Trying to change "+key+" to "+cmd)
|
||||
try:
|
||||
cmd = type(data[key])(cmd)
|
||||
data[key] = cmd
|
||||
self.bot.log("Did that")
|
||||
return data
|
||||
except:
|
||||
self.bot.log("No. That did not work")
|
||||
return "Wrong data type"
|
||||
else:
|
||||
self.bot.log("Yeah, that didn't work")
|
||||
return "Wrong data type"
|
||||
else:
|
||||
self.bot.log("Couldn't find "+key)
|
||||
self.bot.log("Testing to see if it's a multi-word key")
|
||||
cmd = key + " " + cmd
|
||||
words = cmd.split(" ")
|
||||
search = ""
|
||||
i = 0
|
||||
while search not in data:
|
||||
try:
|
||||
search += " " + words[i]
|
||||
i += 1
|
||||
except:
|
||||
self.bot.log("It wasn't. "+search+" doesn't exist")
|
||||
return search + " doesn't exist"
|
||||
if search[0] == " ":
|
||||
search = search[1:]
|
||||
self.bot.log("Yeah! Did that! The key was "+search)
|
||||
|
||||
cmd = cmd[len(search):]
|
||||
if cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
|
||||
if cmd == "":
|
||||
self.bot.log("Returning "+search)
|
||||
return self.setUpDict(data[search])
|
||||
else:
|
||||
newKey = cmd.split(" ")[0]
|
||||
cmd = cmd[len(newKey):]
|
||||
if cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
lookUpResult = self.lookUp(data[search],newKey,cmd)
|
||||
if type(lookUpResult) is dict:
|
||||
data[search] = lookUpResult
|
||||
return data
|
||||
else:
|
||||
return lookUpResult
|
||||
|
||||
def characterSheet(self,character : dict):
|
||||
self.bot.log("Setting up a character sheet for "+character["Name"])
|
||||
divider = "--------------------\n"
|
||||
name = character["Name"]
|
||||
textf = ""
|
||||
if character["Force-rating"] != 0:
|
||||
textf = "\n**Force Rating**: "+str(character["Force-rating"])
|
||||
|
||||
text1 = "**Species**: "+character["Species"]+"\n**Career**: "+character["Career"]+"\n**Specialization Trees**: "+", ".join(character["Specialization-trees"])+textf+"\n**Soak**: "+str(character["Soak"])
|
||||
text2 = "\n\n**Wounds**: "+str(character["Wounds"])+"/"+str(character["Wound-threshold"])+"\n**Strain**: "+str(character["Strain"])+"/"+str(character["Strain-threshold"])
|
||||
text3 = self.setUpDict(character["Characteristics"])
|
||||
text4 = self.setUpDict(character["Skills"])
|
||||
text5 = ""
|
||||
text6 = ""
|
||||
text7 = ""
|
||||
text8 = ""
|
||||
|
||||
if bool(character["Talents"]):
|
||||
text5 = "**Talents**: "+", ".join(list(character["Talents"]))+"\n\n"
|
||||
|
||||
if bool(character["Force-powers"]):
|
||||
text6 = "**Force Powers**: "+", ".join(list(character["Force-powers"]))+"\n\n"
|
||||
|
||||
text7 = "**Equipment**: "+", ".join(character["Equipment"])+"\n**Credits**: "+str(character["Credits"])+"\n**Weapons**: "+", ".join(list(character["Weapons"]))+"\n"+divider
|
||||
|
||||
if bool(character["Obligations"]):
|
||||
text8 = "**Obligations**: "+",".join(list(character["Obligations"]))
|
||||
|
||||
return name, text1+text2+"\n\n"+text3+divider+text4+"\n"+divider+text5+text6+text7+text8
|
||||
|
||||
def charData(self,user : str,cmd : str):
|
||||
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
|
||||
|
||||
key = string.capwords(cmd.split(" ")[0])
|
||||
cmd = cmd[len(key):]
|
||||
if cmd == " ":
|
||||
cmd = ""
|
||||
elif cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
|
||||
self.bot.log("Looking for "+self.bot.database_funcs.get_name(user)+"'s character")
|
||||
if userCharacter != None:
|
||||
self.bot.log("Found it! Looking for "+key+" in the data")
|
||||
if key in userCharacter:
|
||||
self.bot.log("Found it!")
|
||||
if type(userCharacter[key]) is dict:
|
||||
self.bot.log("It's a dictionary!")
|
||||
if cmd == "":
|
||||
self.bot.log("Retrieving data")
|
||||
if key == "Weapons":
|
||||
if bool(userCharacter[key]):
|
||||
self.bot.log("Returning a list of weapons")
|
||||
return ", ".join(list(userCharacter[key]))
|
||||
else:
|
||||
self.bot.log("The character doesn't have any weapons. Which is probably for the best. Like, who just walks around with weapons? (error code 941)")
|
||||
return "There doesn't seem to be anything there... (error code 941)"
|
||||
else:
|
||||
return self.setUpDict(userCharacter[key])
|
||||
elif cmd[0] == "+":
|
||||
self.bot.log("Gonna add something!!!")
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("Nope. That didn't happen (error code 942)")
|
||||
return "Can't do that (error code 942)"
|
||||
|
||||
if (key == "Talents" or key == "Force-powers") and "," in cmd:
|
||||
cmd = cmd.split(",")
|
||||
while cmd[1][0] == " ":
|
||||
cmd[1] = cmd[1][1:]
|
||||
self.bot.log("Adding "+cmd[0]+" to "+key)
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key+"."+cmd[0] : cmd[1]}})
|
||||
return cmd[0]+" added to "+key+" for " + userCharacter["Name"]
|
||||
|
||||
elif key == "Obligations" and "," in cmd:
|
||||
cmd = cmd.split(",")
|
||||
while cmd[1][0] == " ":
|
||||
cmd[1] = cmd[1][1:]
|
||||
self.bot.log("Adding "+cmd[0]+" to "+key)
|
||||
try:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key+"."+cmd[0] : int(cmd[1])}})
|
||||
except:
|
||||
self.bot.log("Fucked that up (error code 949)")
|
||||
return "Wrong data type (error code 949)"
|
||||
return cmd[0]+" added to "+key+" for " + userCharacter["Name"]
|
||||
|
||||
elif key == "Weapons":
|
||||
with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f:
|
||||
templates = json.load(f)
|
||||
newWeapon = templates["Weapon"]
|
||||
self.bot.log("Adding "+cmd+" to "+key)
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key+"."+cmd : newWeapon}})
|
||||
|
||||
return cmd+" added to weapons for " + userCharacter["Name"]
|
||||
|
||||
else:
|
||||
self.bot.log("That's not happening (error code 947d)")
|
||||
return "Can't add that (error code 947d)"
|
||||
|
||||
elif cmd[0] == "-":
|
||||
self.bot.log("Gonna subtract/remove something")
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("AAAAAAAAAAAA (error code 948)")
|
||||
return "Can't do that (error code 948)"
|
||||
|
||||
if key == "Talents" or key == "Force-powers" or key == "Weapons" or key == "Obligations":
|
||||
self.bot.log("Trying to remove "+cmd+" from "+key)
|
||||
if cmd in userCharacter[key]:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$unset": {cmd}})
|
||||
self.bot.log("I did that")
|
||||
return cmd+" removed from "+key+" from "+userCharacter["Name"]
|
||||
else:
|
||||
self.bot.log("Welp. I fucked that up (error code 946e)")
|
||||
return "Can't remove that (error code 946e)"
|
||||
else:
|
||||
self.bot.log("Urgh! (error code 946d)")
|
||||
return "Can't remove that (error code 946d)"
|
||||
|
||||
else:
|
||||
self.bot.log("Looking up "+cmd+" in "+key)
|
||||
if key == "Talents" or key == "Force-powers":
|
||||
newKey = cmd
|
||||
newcmd = ""
|
||||
else:
|
||||
newKey = string.capwords(cmd.split(" ")[0])
|
||||
newcmd = cmd[len(newKey):]
|
||||
|
||||
lookUpResult = self.lookUp(userCharacter[key],newKey,newcmd)
|
||||
|
||||
if type(lookUpResult) is dict:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key : lookUpResult}})
|
||||
return "Changed " + userCharacter["Name"] + "'s " + key
|
||||
else:
|
||||
return lookUpResult
|
||||
else:
|
||||
if cmd == "":
|
||||
self.bot.log("Retrieving data")
|
||||
if type(userCharacter[key]) is list:
|
||||
return key+":\n"+", ".join(userCharacter[key])
|
||||
else:
|
||||
return userCharacter[key]
|
||||
elif cmd[0] == '+':
|
||||
self.bot.log("Adding")
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("Error message (error code 948)")
|
||||
return "Can't do that (error code 948)"
|
||||
|
||||
if type(userCharacter[key]) is int:
|
||||
try:
|
||||
self.bot.log("Adding "+cmd+" to "+key)
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$inc": {key : int(cmd)}})
|
||||
return "Added " + cmd + " to " + userCharacter["Name"] + "'s " + key
|
||||
except:
|
||||
self.bot.log("BITCH SANDWICH (error code 947c)")
|
||||
return "Can't add that (error code 947c)"
|
||||
elif type(userCharacter[key]) is list:
|
||||
try:
|
||||
self.bot.log("Adding "+cmd+" to "+key)
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$push": {key : cmd}})
|
||||
return "Added " + cmd + " to " + userCharacter["Name"] + "'s " + key
|
||||
except:
|
||||
self.bot.log("tstststststs (error code 947b)")
|
||||
return "Can't add that (error code 947b)"
|
||||
else:
|
||||
self.bot.log("Help (error code 947a)")
|
||||
return "Can't add that (error code 947a)"
|
||||
elif cmd[0] == '-':
|
||||
self.bot.log("Removing/subtracting")
|
||||
try:
|
||||
cmd = cmd[1:]
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
except:
|
||||
self.bot.log("lalalala (error code 948)")
|
||||
return "Can't do that (error code 948)"
|
||||
|
||||
if type(userCharacter[key]) is int:
|
||||
try:
|
||||
self.bot.log("Subtracting "+cmd+" from "+key)
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$inc": {key : -int(cmd)}})
|
||||
return "Subtracted " + cmd + " from " + userCharacter["Name"] + "'s " + key
|
||||
except:
|
||||
self.bot.log("Tried it. Didn't want to (error code 946c)")
|
||||
return "Can't remove that (error code 946c)"
|
||||
elif type(userCharacter[key]) is list:
|
||||
try:
|
||||
self.bot.log("removing "+cmd+" from "+key)
|
||||
try:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$pull": {key : cmd}})
|
||||
except:
|
||||
self.bot.log("They can only remove stuff that's actually in the list")
|
||||
return "Not in list (error code 944b)"
|
||||
return "Removed " + cmd + " from " + userCharacter["Name"] + "'s " + key
|
||||
except:
|
||||
self.bot.log("nah (error code 946b)")
|
||||
return "Can't remove that (error code 946b)"
|
||||
else:
|
||||
self.bot.log("nyope (error code 946a)")
|
||||
return "Can't remove that (error code 946a)"
|
||||
else:
|
||||
self.bot.log("Changing "+key+" to "+cmd)
|
||||
if type(userCharacter[key]) is int:
|
||||
try:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key : int(cmd)}})
|
||||
except:
|
||||
self.bot.log("I don't wanna tho (error code 945b)")
|
||||
return "Can't do that (error code 945b)"
|
||||
elif type(userCharacter[key]) is str:
|
||||
self.bot.database["starwars characters"].update_one({"_id":user},
|
||||
{"$set": {key : cmd}})
|
||||
else:
|
||||
self.bot.log("I don't wanna tho (error code 945a)")
|
||||
return "Can't do that (error code 945a)"
|
||||
return "Changed " + userCharacter["Name"] + "'s " + key +" to " + cmd
|
||||
else:
|
||||
self.bot.log(key+" isn't in there (error code 944)")
|
||||
return "Couldn't find that data. Are you sure you spelled it correctly? (error code 944)"
|
||||
else:
|
||||
self.bot.log(user+" doesn't have a character (error code 943)")
|
||||
return "You don't have a character. You can make one with /starwarscharacter (error code 943)"
|
||||
|
||||
def replaceSpaces(self,cmd : str):
|
||||
withSpaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - Light","Lightsaber Characteristic","Critical Injuries","Force Powers"]
|
||||
withoutSpaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"]
|
||||
|
||||
for x, value in enumerate(withoutSpaces):
|
||||
cmd = cmd.replace(withSpaces[x],value)
|
||||
|
||||
return cmd
|
||||
|
||||
def replaceWithSpaces(self,cmd : str):
|
||||
withSpaces = ["Specialization Trees","Wound Threshold","Strain Threshold","Defense - Ranged","Defense - Melee","Force Rating","Core Worlds","Outer Rim","Piloting - Planetary","Piloting - Space","Ranged - Heavy","Ranged - light","Lightsaber Characteristic","Critical Injuries","Force Powers"]
|
||||
withoutSpaces = ["Specialization-trees","Wound-threshold","Strain-threshold","Defense-ranged","Defense-melee","Force-rating","Core-worlds","Outer-rim","Piloting-planetary","Piloting-space","Ranged-heavy","Ranged-light","Lightsaber-characteristic","Critical-injuries","Force-powers"]
|
||||
|
||||
for x, value in enumerate(withoutSpaces):
|
||||
cmd = cmd.replace(value,withSpaces[x])
|
||||
|
||||
return cmd
|
||||
|
||||
async def parseChar(self, ctx, parameters : str):
|
||||
user = f"#{ctx.author.id}"
|
||||
cmd = string.capwords(parameters.replace("+","+ ").replace("-","- ").replace(",",", "))
|
||||
returnEmbed = False
|
||||
|
||||
cmd = self.replaceSpaces(cmd)
|
||||
|
||||
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
|
||||
|
||||
if cmd == " ":
|
||||
cmd = ""
|
||||
elif cmd != "":
|
||||
while cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
|
||||
|
||||
if cmd == "":
|
||||
if userCharacter != None:
|
||||
title, text = self.characterSheet(userCharacter)
|
||||
text = self.replaceWithSpaces(text)
|
||||
returnEmbed = True
|
||||
else:
|
||||
self.bot.log("Makin' a character for "+self.bot.database_funcs.get_name(user))
|
||||
with open("gwendolyn/resources/star_wars/starwarstemplates.json", "r") as f:
|
||||
templates = json.load(f)
|
||||
newChar = templates["Character"]
|
||||
newChar["_id"] = user
|
||||
self.bot.database["starwars characters"].insert_one(newChar)
|
||||
await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " created")
|
||||
else:
|
||||
if cmd == "Purge":
|
||||
self.bot.log("Deleting "+self.bot.database_funcs.get_name(user)+"'s character")
|
||||
self.bot.database["starwars characters"].delete_one({"_id":user})
|
||||
await ctx.send("Character for " + self.bot.database_funcs.get_name(user) + " deleted")
|
||||
else:
|
||||
await ctx.send(self.replaceWithSpaces(str(self.charData(user,cmd))))
|
||||
|
||||
if returnEmbed:
|
||||
em = discord.Embed(title = title, description = text, colour=0xDEADBF)
|
||||
await ctx.send(embed = em)
|
||||
|
||||
|
||||
|
||||
def lightsaberChar(self,user : str):
|
||||
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
|
||||
|
||||
if userCharacter != None:
|
||||
return userCharacter["Lightsaber-characteristic"]
|
||||
|
||||
def userHasChar(self,user : str):
|
||||
userCharacter = self.bot.database["starwars characters"].find_one({"_id":user})
|
||||
|
||||
return userCharacter != None
|
||||
|
72
gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py
Normal file
72
gwendolyn/funcs/star_wars_funcs/star_wars_destiny.py
Normal file
@ -0,0 +1,72 @@
|
||||
class StarWarsDestiny():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
def destinyNew(self, num : int):
|
||||
self.bot.log("Creating a new destiny pool with "+str(num)+" players")
|
||||
roll, diceResults = self.bot.star_wars.roll.roll(0,0,0,0,0,0,num)
|
||||
roll = "".join(sorted(roll))
|
||||
|
||||
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
|
||||
f.write(roll)
|
||||
|
||||
return "Rolled for Destiny Points and got:\n"+self.bot.star_wars.roll.diceResultToEmoji(diceResults)+"\n"+self.bot.star_wars.roll.resultToEmoji(roll)
|
||||
|
||||
def destinyUse(self, user : str):
|
||||
with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f:
|
||||
points = f.read()
|
||||
|
||||
if user == "Nikolaj":
|
||||
self.bot.log("Trying to use a dark side destiny point")
|
||||
if 'B' in points:
|
||||
points = points.replace("B","L",1)
|
||||
points = "".join(sorted(points))
|
||||
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
|
||||
f.write(points)
|
||||
self.bot.log("Did it")
|
||||
return "Used a dark side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points)
|
||||
else:
|
||||
self.bot.log("There were no dark side destiny points")
|
||||
return "No dark side destiny points"
|
||||
else:
|
||||
self.bot.log("Trying to use a light side destiny point")
|
||||
if 'L' in points:
|
||||
points = points.replace("L","B",1)
|
||||
points = "".join(sorted(points))
|
||||
with open("gwendolyn/resources/star_wars/destinyPoints.txt","wt") as f:
|
||||
f.write(points)
|
||||
self.bot.log("Did it")
|
||||
return "Used a light side destiny point. Destiny pool is now:\n"+self.bot.star_wars.roll.resultToEmoji(points)
|
||||
else:
|
||||
self.bot.log("There were no dark side destiny points")
|
||||
return "No light side destiny points"
|
||||
|
||||
async def parseDestiny(self, ctx, cmd : str):
|
||||
user = f"#{ctx.author.id}"
|
||||
if cmd != "":
|
||||
while cmd[0] == ' ':
|
||||
cmd = cmd[1:]
|
||||
if cmd == "":
|
||||
break
|
||||
|
||||
if cmd == "":
|
||||
self.bot.log("Retrieving destiny pool info")
|
||||
with open("gwendolyn/resources/star_wars/destinyPoints.txt","rt") as f:
|
||||
sendMessage = self.bot.star_wars.roll.resultToEmoji(f.read())
|
||||
else:
|
||||
commands = cmd.upper().split(" ")
|
||||
if commands[0] == "N":
|
||||
if len(commands) > 1:
|
||||
sendMessage = self.destinyNew(int(commands[1]))
|
||||
else:
|
||||
sendMessage = "You need to give an amount of players (error code 921)"
|
||||
elif commands[0] == "U":
|
||||
sendMessage = self.destinyUse(user)
|
||||
else:
|
||||
sendMessage = "I didn't quite understand that (error code 922)"
|
||||
|
||||
messageList = sendMessage.split("\n")
|
||||
await ctx.send(messageList[0])
|
||||
if len(messageList) > 1:
|
||||
for messageItem in messageList[1:]:
|
||||
await ctx.channel.send(messageItem)
|
391
gwendolyn/funcs/star_wars_funcs/star_wars_roll.py
Normal file
391
gwendolyn/funcs/star_wars_funcs/star_wars_roll.py
Normal file
@ -0,0 +1,391 @@
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import json
|
||||
|
||||
with open("gwendolyn/resources/star_wars/starwarsskills.json", "r") as f:
|
||||
skillData = json.load(f)
|
||||
|
||||
class StarWarsRoll():
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
# Rolls the specified dice
|
||||
def roll(self, abi : int = 1, prof : int = 0, dif : int = 3, cha : int = 0, boo : int = 0, setb : int = 0, force : int = 0):
|
||||
result = ""
|
||||
diceResult = []
|
||||
for _ in range(abi):
|
||||
choice = random.choice(["","S","S","SS","A","A","SA","AA"])
|
||||
result += choice
|
||||
diceResult.append("abi"+choice)
|
||||
|
||||
for _ in range(prof):
|
||||
choice = random.choice(["","S","S","SS","SS","A","SA","SA","SA","AA","AA","R"])
|
||||
result += choice
|
||||
diceResult.append("prof"+choice)
|
||||
|
||||
for _ in range(dif):
|
||||
choice = random.choice(["","F","FF","H","H","H","HH","FH"])
|
||||
result += choice
|
||||
diceResult.append("dif"+choice)
|
||||
|
||||
for _ in range(cha):
|
||||
choice = random.choice(["","F","F","FF","FF","H","H","FH","FH","HH","HH","D"])
|
||||
result += choice
|
||||
diceResult.append("cha"+choice)
|
||||
|
||||
for _ in range(boo):
|
||||
choice = random.choice(["","","S","SA","AA","A"])
|
||||
result += choice
|
||||
diceResult.append("boo"+choice)
|
||||
|
||||
for _ in range(setb):
|
||||
choice = random.choice(["","","F","F","H","H"])
|
||||
result += choice
|
||||
diceResult.append("setb"+choice)
|
||||
|
||||
for _ in range (force):
|
||||
choice = random.choice(["B","B","B","B","B","B","BB","L","L","LL","LL","LL"])
|
||||
result += choice
|
||||
diceResult.append("force"+choice)
|
||||
|
||||
return result, diceResult
|
||||
|
||||
# Lets dice cancel each other out
|
||||
def simplify(self, result : str):
|
||||
self.bot.log("Simplifying "+result)
|
||||
simp = ""
|
||||
success = (result.count('S') + result.count('R')) - (result.count('F') + result.count('D'))
|
||||
advantage = result.count('A') - result.count('H')
|
||||
result = re.sub("S|A|F|H","",result)
|
||||
|
||||
if success > 0:
|
||||
for _ in range(success):
|
||||
simp += "S"
|
||||
elif success < 0:
|
||||
for _ in range(abs(success)):
|
||||
simp += "F"
|
||||
|
||||
if advantage > 0:
|
||||
for _ in range(advantage):
|
||||
simp += "A"
|
||||
elif advantage < 0:
|
||||
for _ in range(abs(advantage)):
|
||||
simp += "H"
|
||||
|
||||
simp += result
|
||||
|
||||
return simp
|
||||
|
||||
# Returns emoji that symbolize the dice results
|
||||
def diceResultToEmoji(self, diceResults : list):
|
||||
emoji = ""
|
||||
for result in diceResults:
|
||||
if result == "abiA":
|
||||
emoji += "<:abil1a:695267684476125264> "
|
||||
elif result == "abiSA":
|
||||
emoji += "<:abil1a1s:695267684484513842> "
|
||||
elif result == "abiS":
|
||||
emoji += "<:abil1s:695267684514005013> "
|
||||
elif result == "abiAA":
|
||||
emoji += "<:abil2a:695267684547428352> "
|
||||
elif result == "abiSS":
|
||||
emoji += "<:abil2s:695267684761206914> "
|
||||
elif result == "abi":
|
||||
emoji += "<:abilbla:695267684660674602> "
|
||||
|
||||
elif result == "profA":
|
||||
emoji += "<:prof1a:695267685361123338> "
|
||||
elif result == "profSA":
|
||||
emoji += "<:prof1a1s:695267685067653140> "
|
||||
elif result == "profR":
|
||||
emoji += "<:prof1r:695267685067522088> "
|
||||
elif result == "profS":
|
||||
emoji += "<:prof1s:695267684899881012> "
|
||||
elif result == "profAA":
|
||||
emoji += "<:prof2a:695267684996218982> "
|
||||
elif result == "profSS":
|
||||
emoji += "<:prof2s:695267684878647327> "
|
||||
elif result == "prof":
|
||||
emoji += "<:profbla:695267684698292235> "
|
||||
|
||||
elif result == "difF":
|
||||
emoji += "<:dif1f:695267684924915804> "
|
||||
elif result == "difH":
|
||||
emoji += "<:dif1h:695267684908138506> "
|
||||
elif result == "difFH":
|
||||
emoji += "<:dif1h1f:695267684908269678> "
|
||||
elif result == "difFF":
|
||||
emoji += "<:dif2f:695267684924784680> "
|
||||
elif result == "difHH":
|
||||
emoji += "<:dif2h:695267685071585340> "
|
||||
elif result == "dif":
|
||||
emoji += "<:difbla:695267685000544276> "
|
||||
|
||||
elif result == "chaD":
|
||||
emoji += "<:cha1d:695267684962533447> "
|
||||
elif result == "chaF":
|
||||
emoji += "<:cha1f:695267684601954346> "
|
||||
elif result == "chaH":
|
||||
emoji += "<:cha1h:695267685046681620> "
|
||||
elif result == "chaFH":
|
||||
emoji += "<:cha1h1f:695267685063327784> "
|
||||
elif result == "chaFF":
|
||||
emoji += "<:cha2f:695267684832641097> "
|
||||
elif result == "chaHH":
|
||||
emoji += "<:cha2h:695267684631183381> "
|
||||
elif result == "cha":
|
||||
emoji += "<:chabla:695267684895686787> "
|
||||
|
||||
elif result == "booA":
|
||||
emoji += "<:boo1a:695267684975116329> "
|
||||
elif result == "booSA":
|
||||
emoji += "<:boo1a1s:695267684970922024> "
|
||||
elif result == "booS":
|
||||
emoji += "<:boo1s:695267684979441714> "
|
||||
elif result == "booAA":
|
||||
emoji += "<:boo2a:695267685100945488> "
|
||||
elif result == "boo":
|
||||
emoji += "<:boobla:695267684757012550> "
|
||||
|
||||
elif result == "setbF":
|
||||
emoji += "<:set1f:695267685054939197> "
|
||||
elif result == "setbH":
|
||||
emoji += "<:set1h:695267685147082802> "
|
||||
elif result == "setb":
|
||||
emoji += "<:setbla:695267685151408169> "
|
||||
|
||||
elif result == "forceB":
|
||||
emoji += "<:for1b:695267684593434677> "
|
||||
elif result == "forceL":
|
||||
emoji += "<:for1l:695267684606148640> "
|
||||
elif result == "forceBB":
|
||||
emoji += "<:for2b:695267684903944303> "
|
||||
elif result == "forceLL":
|
||||
emoji += "<:for2l:695267684992024626> "
|
||||
|
||||
return emoji
|
||||
|
||||
# Returns emoji that symbolize the results of the dice rolls
|
||||
def resultToEmoji(self, result : str):
|
||||
emoji = ""
|
||||
for char in result:
|
||||
if char == 'S':
|
||||
emoji += "<:success:826026925280854026> "
|
||||
elif char == 'A':
|
||||
emoji += "<:advantage:826026925515604009> "
|
||||
elif char == 'R':
|
||||
emoji += "<:triumph:826026925319127070> "
|
||||
elif char == 'F':
|
||||
emoji += "<:failure:826026925288980511> "
|
||||
elif char == 'H':
|
||||
emoji += "<:threat:826026925280985108> "
|
||||
elif char == 'D':
|
||||
emoji += "<:despair:826026925272203294> "
|
||||
elif char == 'L':
|
||||
emoji += "<:light:826026925059211295>"
|
||||
elif char == 'B':
|
||||
emoji += "<:dark:826026925289373717>"
|
||||
|
||||
return emoji
|
||||
|
||||
# Converts emoji into letters
|
||||
def emojiToResult(self, emoji : str):
|
||||
result = ""
|
||||
for char in emoji:
|
||||
if char == "<:light:691010089905029171>":
|
||||
emoji += 'L'
|
||||
if char == "<:dark:691010101901000852>":
|
||||
emoji += 'B'
|
||||
|
||||
return result
|
||||
|
||||
# Returns emoji that symbolize the dice
|
||||
def diceToEmoji(self, dice : list):
|
||||
emoji = ""
|
||||
|
||||
for _ in range(dice[0]):
|
||||
emoji += "<:ability:690974213397282826> "
|
||||
for _ in range(dice[1]):
|
||||
emoji += "<:proficiency:690973435354153071> "
|
||||
for _ in range(dice[2]):
|
||||
emoji += "<:difficulty:690973992470708296> "
|
||||
for _ in range(dice[3]):
|
||||
emoji += "<:challenge:690973419906400306> "
|
||||
for _ in range(dice[4]):
|
||||
emoji += "<:boost:690972178216386561> "
|
||||
for _ in range(dice[5]):
|
||||
emoji += "<:setback:690972157890658415> "
|
||||
for _ in range(dice[6]):
|
||||
emoji += "<:force:690973451883774013> "
|
||||
|
||||
return emoji
|
||||
|
||||
# Rolls for obligation
|
||||
def obligationRoll(self):
|
||||
self.bot.log("Rolling for obligation")
|
||||
data = self.bot.database["starwarscharacters"]
|
||||
|
||||
table = []
|
||||
|
||||
for character in data:
|
||||
for obligation in data[character]["Obligations"]:
|
||||
for _ in range(data[character]["Obligations"][obligation]):
|
||||
table.append(data[character]["Name"]+", "+obligation)
|
||||
|
||||
while len(table) < 100:
|
||||
table.append("Nothing")
|
||||
|
||||
return random.choice(table)
|
||||
|
||||
# Rolls for critical injury
|
||||
async def critRoll(self, ctx, addington : int):
|
||||
dd = "<:difficulty:690973992470708296>"
|
||||
sd = "<:setback:690972157890658415>"
|
||||
bd = "<:boost:690972178216386561>"
|
||||
roll = random.randint(1,100) + addington
|
||||
injuries = [
|
||||
"**Minor nick**: The target suffers 1 strain, "+dd] * 5 + [
|
||||
"**Slowed down**: The target can only act during the last allied initiative slot this turn, "+dd] * 5 + [
|
||||
"**Sudden Jolt**: The target drops whatever is in hand, "+dd] * 5 + [
|
||||
"**Distracted**: The target cannot perform a Free maneuver during his next turn, "+dd] * 5 + [
|
||||
"**Off-Balance**: The target adds "+sd+" to his next skill check, "+dd] * 5 + [
|
||||
"**Discouraging Wound**: Flip one light side Destiny point to a dark side Destiny point (reverse if NPC), "+dd] * 5 + [
|
||||
"**Stunned**: The target is staggered until the end of his next turn, "+dd] * 5 + [
|
||||
"**Stinger**: Increase the difficulty of next check by one, "+dd] * 5 + [
|
||||
"**Bowled Over**: The target is knocked prone and suffers 1 strain, "+dd+dd] * 5 + [
|
||||
"**Head Ringer**: The target increases the difficulty of all Intellect and Cunning checks by one until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Fearsome Wound**: The target increases the difficulty of all Presence and Willpower checks by one until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Agonizing Wound**: The target increases the difficulty of all Brawn and Agility checks by one until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Slightly Dazed**: The target is disoriented until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Scattered Senses**: The target removes all "+bd+" from skill checks until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Hamstrung**: The target loses his free maneuver until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**Overpowered**: The target leaves himself open, and the attacker may immediately attempt another free attack agains him, using the exact same pool as the original, "+dd+dd] * 5 + [
|
||||
"**Winded**: Until the end of the encounter, the target cannot voluntarily suffer strain to activate any abilities or gain additional maneuvers, "+dd+dd] * 5 + [
|
||||
"**Compromised**: Incerase difficulty of all skill checks by one until the end of the encounter, "+dd+dd] * 5 + [
|
||||
"**At the brink**: The target suffers 1 strain each time he performs an action, "+dd+dd+dd] * 5 + [
|
||||
"**Crippled**: One of the target's limbs (selected by the GM) is crippled until healed or replaced. Increase difficulty of all checks that require use of that limb by one, "+dd+dd+dd] * 5 + [
|
||||
"**Maimed**: One of the target's limbs (selected by the GM) is permanently lost. Unless the target has a cybernetic replacement, the target cannot perform actions that would require the use of that limb. All other actions gain "+sd+", "+dd+dd+dd] * 5 + [
|
||||
"HI"] * 5 + [
|
||||
"**Temporarily Lame**: Until this critical injury is healed, the target cannot perform more than one maneuver during his turn, "+dd+dd+dd] * 5 + [
|
||||
"**Blinded**: The target can no longer see. Upgrade the difficulty of all checks twice. Upgrade the difficulty of perception checks three times, "+dd+dd+dd] * 5 + [
|
||||
"**Knocked Senseless**: The target is staggered for the remainder of the encounter, "+dd+dd+dd] * 5 + [
|
||||
"GI"] * 5 + [
|
||||
"**Bleeding Out**: Every round, the target suffers 1 wound and 1 strain at the beginning of his turn. For every five wounds he suffers beyond his wound threshold, he suffers one additional critical injury. (If he suffers this one again, roll again), "+dd+dd+dd+dd] * 10 + [
|
||||
"**The End is Nigh**: The target will die after the last initiative slot during the next round, "+dd+dd+dd+dd] * 10 + [
|
||||
"**Dead**: U B Dead :("]
|
||||
|
||||
if roll >= len(injuries):
|
||||
results = injuries[-1]
|
||||
else:
|
||||
results = injuries[roll]
|
||||
|
||||
if results == "HI":
|
||||
characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"])
|
||||
results = "**Horrific Injury**: Until this criticil injury is healed, treat the target's "+characteristic+" as if it's one lower, "+dd+dd+dd
|
||||
|
||||
if results == "GI":
|
||||
characteristic = random.choice(["brawn"] * 3 + ["agility"] * 3 + ["intellect", "cunning", "presence"])
|
||||
results = "**Gruesome Injury**: The target's "+characteristic+" is permanently one lower, "+dd+dd+dd+dd
|
||||
|
||||
sendMessage = "Roll: "+str(roll)+"\nInjury:\n"+results
|
||||
|
||||
messageList = sendMessage.split("\n")
|
||||
await ctx.send(messageList[0])
|
||||
if len(messageList) > 1:
|
||||
for messageItem in messageList[1:]:
|
||||
await ctx.channel.send(messageItem)
|
||||
|
||||
# Parses the command into something the other functions understand
|
||||
async def parseRoll(self, ctx, cmd : str = ""):
|
||||
user = f"#{ctx.author.id}"
|
||||
cmd = re.sub(' +',' ',cmd.upper()) + " "
|
||||
if cmd[0] == " ":
|
||||
cmd = cmd[1:]
|
||||
cmd = self.bot.star_wars.character.replaceSpaces(string.capwords(cmd))
|
||||
commands = cmd.split(" ")
|
||||
validCommand = False
|
||||
|
||||
if commands[0] == "":
|
||||
rollParameters = [1,0,3,0,0,0,0]
|
||||
else:
|
||||
rollParameters = [0,0,0,0,0,0,0]
|
||||
|
||||
if string.capwords(commands[0]) == "Obligations":
|
||||
sendMessage = self.obligationRoll()
|
||||
|
||||
elif string.capwords(commands[0]) in skillData:
|
||||
self.bot.log("Oh look! This guy has skills!")
|
||||
if self.bot.star_wars.character.userHasChar(user):
|
||||
self.bot.log("They have a character. That much we know")
|
||||
skillLevel = self.bot.star_wars.character.charData(user,"Skills " + string.capwords(commands[0]))
|
||||
|
||||
if string.capwords(commands[0]) == "Lightsaber":
|
||||
self.bot.log("The skill is lightsaber")
|
||||
charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + self.bot.star_wars.character.lightsaberChar(user))
|
||||
else:
|
||||
charLevel = self.bot.star_wars.character.charData(user,"Characteristics " + skillData[string.capwords(commands[0])])
|
||||
|
||||
abilityDice = abs(charLevel-skillLevel)
|
||||
proficiencyDice = min(skillLevel,charLevel)
|
||||
|
||||
commands = [str(abilityDice)] + [str(proficiencyDice)] + commands[1:]
|
||||
self.bot.log("Converted skill to dice")
|
||||
validCommand = True
|
||||
else:
|
||||
self.bot.log("Okay, no they don't i guess")
|
||||
sendMessage = "You don't have a user. You can make one with /starwarscharacter"
|
||||
|
||||
elif string.capwords(commands[0]) in ["Ranged","Piloting"]:
|
||||
self.bot.log("They fucked up writing the name of a ranged or piloting skill")
|
||||
if string.capwords(commands[0]) == "Ranged":
|
||||
sendMessage = "Did you mean \"Ranged - Heavy\" or \"Ranged - Light\" (error code 913)"
|
||||
else:
|
||||
sendMessage = "Did you mean \"Piloting - Planetary\" or \"Piloting - Space\" (error code 913)"
|
||||
else:
|
||||
validCommand = True
|
||||
|
||||
if validCommand:
|
||||
self.bot.log("Converting commands to dice")
|
||||
for x, command in enumerate(commands):
|
||||
if command != "":
|
||||
command = command.upper()
|
||||
if command[0] == "A":
|
||||
rollParameters[0] = int(command.replace("A",""))
|
||||
elif command[0] == "P":
|
||||
rollParameters[1] = int(command.replace("P",""))
|
||||
elif command[0] == "D":
|
||||
rollParameters[2] = int(command.replace("D",""))
|
||||
elif command[0] == "C":
|
||||
rollParameters[3] = int(command.replace("C",""))
|
||||
elif command[0] == "B":
|
||||
rollParameters[4] = int(command.replace("B",""))
|
||||
elif command[0] == "S":
|
||||
rollParameters[5] = int(command.replace("S",""))
|
||||
elif command[0] == "F":
|
||||
rollParameters[6] = int(command.replace("F",""))
|
||||
else:
|
||||
rollParameters[x] = int(command)
|
||||
|
||||
self.bot.log("Rolling "+str(rollParameters))
|
||||
rollResults, diceResults = self.roll(rollParameters[0],rollParameters[1],rollParameters[2],rollParameters[3],rollParameters[4],rollParameters[5],rollParameters[6])
|
||||
|
||||
simplified = self.simplify(rollResults)
|
||||
|
||||
name = self.bot.star_wars.character.getCharName(user)
|
||||
|
||||
self.bot.log("Returns results and simplified results")
|
||||
|
||||
if simplified == "":
|
||||
sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\nEverything cancels out!"
|
||||
else:
|
||||
sendMessage = name + " rolls: " + "\n" + self.diceResultToEmoji(diceResults) + "\n" + self.resultToEmoji(simplified)
|
||||
|
||||
messageList = sendMessage.split("\n")
|
||||
await ctx.send(messageList[0])
|
||||
if len(messageList) > 1:
|
||||
for messageItem in messageList[1:]:
|
||||
if messageItem == "":
|
||||
self.bot.log("Tried to send empty message")
|
||||
else:
|
||||
await ctx.channel.send(messageItem)
|
Reference in New Issue
Block a user