import asyncio import collections import copy import datetime import json import time from random import choice import discord from redbot.core import Config, bank, commands from redbot.core.bot import Red from redbot.core.data_manager import bundled_data_path class Gardener(commands.Cog): """Gardener class""" def __init__(self, user: discord.User, config: Config): super().__init__() self.user = user self.config = config self.badges = [] self.points = 0 self.products = {} self.current = {} def __str__(self): return ( "Gardener named {}\n" "Badges: {}\n" "Points: {}\n" "Products: {}\n" "Current: {}".format(self.user, self.badges, self.points, self.products, self.current) ) def __repr__(self): return "{} - {} - {} - {} - {}".format( self.user, self.badges, self.points, self.products, self.current ) async def load_config(self): self.badges = await self.config.user(self.user).badges() self.points = await self.config.user(self.user).points() self.products = await self.config.user(self.user).products() self.current = await self.config.user(self.user).current() async def save_gardener(self): await self.config.user(self.user).badges.set(self.badges) await self.config.user(self.user).points.set(self.points) await self.config.user(self.user).products.set(self.products) await self.config.user(self.user).current.set(self.current) async def is_complete(self, now): message = None if self.current: then = self.current["timestamp"] health = self.current["health"] grow_time = self.current["time"] badge = self.current["badge"] reward = self.current["reward"] if (now - then) > grow_time: self.points += reward if badge not in self.badges: self.badges.append(badge) message = ( "Your plant made it! " "You are rewarded with the **{}** badge and you have received **{}** Thneeds.".format( badge, reward ) ) if health < 0: message = "Your plant died!" if message is not None: self.current = {} await self.save_gardener() await self.user.send(message) async def _die_in(gardener, degradation): # # Calculating how much time in minutes remains until the plant's health hits 0 # return int(gardener.current["health"] / degradation.degradation) async def _grow_time(gardener): # # Calculating the remaining grow time for a plant # now = int(time.time()) then = gardener.current["timestamp"] return (gardener.current["time"] - (now - then)) / 60 async def _send_message(channel, message): """Sendsa message""" em = discord.Embed(description=message, color=discord.Color.green()) await channel.send(embed=em) async def _withdraw_points(gardener: Gardener, amount): # # Substract points from the gardener # if (gardener.points - amount) < 0: return False else: gardener.points -= amount return True class PlantTycoon(commands.Cog): """Grow your own plants! Be sure to take proper care of it.""" def __init__(self, bot: Red, *args, **kwargs): super().__init__(*args, **kwargs) self.bot = bot self.config = Config.get_conf(self, identifier=80108971101168412199111111110) default_user = {"badges": [], "points": 0, "products": {}, "current": {}} self.config.register_user(**default_user) self.plants = None self.products = None self.defaults = { "points": { "buy": 5, "add_health": 5, "fertilize": 10, "pruning": 20, "pesticide": 25, "growing": 5, "damage": 25, }, "timers": {"degradation": 1, "completion": 1, "notification": 5}, "degradation": {"base_degradation": 1.5}, "notification": {"max_health": 50}, } self.badges = { "badges": { "Flower Power": {}, "Fruit Brute": {}, "Sporadic": {}, "Odd-pod": {}, "Greenfingers": {}, "Nobel Peas Prize": {}, "Annualsary": {}, } } self.notifications = { "messages": [ "The soil seems dry, maybe you could give your plant some water?", "Your plant seems a bit droopy. I would give it some fertilizer if I were you.", "Your plant seems a bit too overgrown. You should probably trim it a bit.", ] } # # Starting loops # self.completion_task = bot.loop.create_task(self.check_completion_loop()) # self.degradation_task = bot.loop.create_task(self.check_degradation()) self.notification_task = bot.loop.create_task(self.send_notification()) # # Loading bank # # self.bank = bot.get_cog('Economy').bank async def _load_plants_products(self): plant_path = bundled_data_path(self) / "plants.json" product_path = bundled_data_path(self) / "products.json" with plant_path.open() as json_data: self.plants = json.load(json_data) await self._load_event_seeds() with product_path.open() as json_data: self.products = json.load(json_data) for product in self.products: print("PlantTycoon: Loaded {}".format(product)) async def _load_event_seeds(self): self.plants["all_plants"] = copy.deepcopy(self.plants["plants"]) plant_options = self.plants["all_plants"] d = datetime.date.today() month = d.month if month == 1: plant_options.append(self.plants["event"]["January"]) elif month == 2: plant_options.append(self.plants["event"]["February"]) elif month == 3: plant_options.append(self.plants["event"]["March"]) elif month == 4: plant_options.append(self.plants["event"]["April"]) elif month == 10: plant_options.append(self.plants["event"]["October"]) elif month == 11: plant_options.append(self.plants["event"]["November"]) elif month == 12: plant_options.append(self.plants["event"]["December"]) async def _gardener(self, user: discord.User) -> Gardener: # # This function returns a Gardener object for the user # g = Gardener(user, self.config) await g.load_config() return g async def _degradation(self, gardener: Gardener): # # Calculating the rate of degradation per check_completion_loop() cycle. # if self.products is None: await self._load_plants_products() modifiers = sum( [ self.products[product]["modifier"] for product in gardener.products if gardener.products[product] > 0 ] ) degradation = ( 100 / (gardener.current["time"] / 60) * (self.defaults["degradation"]["base_degradation"] + gardener.current["degradation"]) ) + modifiers d = collections.namedtuple("degradation", "degradation time modifiers") return d(degradation=degradation, time=gardener.current["time"], modifiers=modifiers) # async def _get_member(self, user_id): # # # # # Return a member object # # # # return discord.User(id=user_id) # I made it a string just to be sure # # async def _send_notification(self, user_id, message): # # # # # Sends a Direct Message to the gardener # # # # member = await self._get_member(user_id) # em = discord.Embed(description=message, color=discord.Color.green()) # await self.bot.send_message(member, embed=em) async def _add_health(self, channel, gardener: Gardener, product, product_category): # # The function to add health # if self.products is None: await self._load_plants_products() product = product.lower() product_category = product_category.lower() if product in self.products and self.products[product]["category"] == product_category: if product in gardener.products: if gardener.products[product] > 0: gardener.current["health"] += self.products[product]["health"] gardener.products[product] -= 1 if gardener.products[product] == 0: del gardener.products[product.lower()] if product_category == "water": emoji = ":sweat_drops:" elif product_category == "fertilizer": emoji = ":poop:" # elif product_category == "tool": else: emoji = ":scissors:" message = "Your plant got some health back! {}".format(emoji) if gardener.current["health"] > gardener.current["threshold"]: gardener.current["health"] -= self.products[product]["damage"] if product_category == "tool": damage_msg = "You used {} too many times!".format(product) else: damage_msg = "You gave too much of {}.".format(product) message = "{} Your plant lost some health. :wilted_rose:".format( damage_msg ) gardener.points += self.defaults["points"]["add_health"] await gardener.save_gardener() else: message = "You have no {}. Go buy some!".format(product) else: if product_category == "tool": message = "You don't have a {}. Go buy one!".format(product) else: message = "You have no {}. Go buy some!".format(product) else: message = "Are you sure you are using {}?".format(product_category) if product_category == "water": emcolor = discord.Color.blue() elif product_category == "fertilizer": emcolor = discord.Color.dark_gold() # elif product_category == "tool": else: emcolor = discord.Color.dark_grey() em = discord.Embed(description=message, color=emcolor) await channel.send(embed=em) @commands.group(name="gardening", autohelp=False) async def _gardening(self, ctx: commands.Context): """Gardening commands.""" if ctx.invoked_subcommand is None: prefix = ctx.prefix title = "**Welcome to Plant Tycoon.**\n" description = """'Grow your own plant. Be sure to take proper care of yours.\n If it successfully grows, you get a reward.\n As you nurture your plant, you gain Thneeds which can be exchanged for credits.\n\n **Commands**\n\n ``{0}gardening seed``: Plant a seed inside the earth.\n ``{0}gardening profile``: Check your gardening profile.\n ``{0}gardening plants``: Look at the list of the available plants.\n ``{0}gardening plant``: Look at the details of a plant.\n ``{0}gardening state``: Check the state of your plant.\n ``{0}gardening buy``: Buy gardening supplies.\n ``{0}gardening convert``: Exchange Thneeds for credits.\n ``{0}shovel``: Shovel your plant out.\n ``{0}water``: Water your plant.\n ``{0}fertilize``: Fertilize the soil.\n ``{0}prune``: Prune your plant.\n""" em = discord.Embed( title=title, description=description.format(prefix), color=discord.Color.green(), ) em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png") em.set_footer( text="This cog was made by SnappyDragon18 and PaddoInWonderland. Inspired by The Lorax (2012)." ) await ctx.send(embed=em) @commands.cooldown(1, 60 * 10, commands.BucketType.user) @_gardening.command(name="seed") async def _seed(self, ctx: commands.Context): """Plant a seed inside the earth.""" if self.plants is None: await self._load_plants_products() author = ctx.author # server = context.message.server # if author.id not in self.gardeners: # self.gardeners[author.id] = {} # self.gardeners[author.id]['current'] = False # self.gardeners[author.id]['points'] = 0 # self.gardeners[author.id]['badges'] = [] # self.gardeners[author.id]['products'] = {} gardener = await self._gardener(author) if not gardener.current: plant_options = self.plants["all_plants"] plant = choice(plant_options) plant["timestamp"] = int(time.time()) plant["degrade_count"] = 0 # index = len(self.plants["plants"]) - 1 # del [self.plants["plants"][index]] message = ( "During one of your many heroic adventures, you came across a mysterious bag that said " '"pick one". To your surprise it had all kinds of different seeds in them. ' "And now that you're home, you want to plant it. " "You went to a local farmer to identify the seed, and the farmer " "said it was {} **{} ({})** seed.\n\n" "Take good care of your seed and water it frequently. " "Once it blooms, something nice might come from it. " "If it dies, however, you will get nothing.".format( plant["article"], plant["name"], plant["rarity"] ) ) if "water" not in gardener.products: gardener.products["water"] = 0 gardener.products["water"] += 5 gardener.current = plant await gardener.save_gardener() em = discord.Embed(description=message, color=discord.Color.green()) else: plant = gardener.current message = "You're already growing {} **{}**, silly.".format( plant["article"], plant["name"] ) em = discord.Embed(description=message, color=discord.Color.green()) await ctx.send(embed=em) @_gardening.command(name="profile") async def _profile(self, ctx: commands.Context, *, member: discord.Member = None): """Check your gardening profile.""" if member is not None: author = member else: author = ctx.author gardener = await self._gardener(author) try: await self._apply_degradation(gardener) except discord.Forbidden: await ctx.send("ERROR\nYou blocked me, didn't you?") em = discord.Embed(color=discord.Color.green()) # , description='\a\n') avatar = author.avatar_url if author.avatar else author.default_avatar_url em.set_author(name="Gardening profile of {}".format(author.name), icon_url=avatar) em.add_field(name="**Thneeds**", value=str(gardener.points)) if not gardener.current: em.add_field(name="**Currently growing**", value="None") else: em.set_thumbnail(url=gardener.current["image"]) em.add_field( name="**Currently growing**", value="{0} ({1:.2f}%)".format( gardener.current["name"], gardener.current["health"] ), ) if not gardener.badges: em.add_field(name="**Badges**", value="None") else: badges = "" for badge in gardener.badges: badges += "{}\n".format(badge.capitalize()) em.add_field(name="**Badges**", value=badges) if not gardener.products: em.add_field(name="**Products**", value="None") else: products = "" for product_name, product_data in gardener.products.items(): if self.products[product_name] is None: continue products += "{} ({}) {}\n".format( product_name.capitalize(), product_data / self.products[product_name]["uses"], self.products[product_name]["modifier"], ) em.add_field(name="**Products**", value=products) if gardener.current: degradation = await self._degradation(gardener) die_in = await _die_in(gardener, degradation) to_grow = await _grow_time(gardener) em.set_footer( text="Total degradation: {0:.2f}% / {1} min (100 / ({2} / 60) * (BaseDegr {3:.2f} + PlantDegr {4:.2f}))" " + ModDegr {5:.2f}) Your plant will die in {6} minutes " "and {7:.1f} minutes to go for flowering.".format( degradation.degradation, self.defaults["timers"]["degradation"], degradation.time, self.defaults["degradation"]["base_degradation"], gardener.current["degradation"], degradation.modifiers, die_in, to_grow, ) ) await ctx.send(embed=em) @_gardening.command(name="plants") async def _plants(self, ctx): """Look at the list of the available plants.""" if self.plants is None: await self._load_plants_products() tick = "" tock = "" tick_tock = 0 for plant in self.plants["all_plants"]: if tick_tock == 0: tick += "**{}**\n".format(plant["name"]) tick_tock = 1 else: tock += "**{}**\n".format(plant["name"]) tick_tock = 0 em = discord.Embed(title="All plants that are growable", color=discord.Color.green()) em.add_field(name="\a", value=tick) em.add_field(name="\a", value=tock) await ctx.send(embed=em) @_gardening.command(name="plant") async def _plant(self, ctx: commands.Context, *, plantname): """Look at the details of a plant.""" if not plantname: await ctx.send_help() if self.plants is None: await self._load_plants_products() t = False plant = None for p in self.plants["all_plants"]: if p["name"].lower() == plantname.lower().strip('"'): plant = p t = True break if t: em = discord.Embed( title="Plant statistics of {}".format(plant["name"]), color=discord.Color.green(), ) em.set_thumbnail(url=plant["image"]) em.add_field(name="**Name**", value=plant["name"]) em.add_field(name="**Rarity**", value=plant["rarity"].capitalize()) em.add_field(name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60)) em.add_field(name="**Damage Threshold**", value="{}%".format(plant["threshold"])) em.add_field(name="**Badge**", value=plant["badge"]) em.add_field(name="**Reward**", value="{} τ".format(plant["reward"])) else: message = "I can't seem to find that plant." em = discord.Embed(description=message, color=discord.Color.red()) await ctx.send(embed=em) @_gardening.command(name="state") async def _state(self, ctx): """Check the state of your plant.""" author = ctx.author gardener = await self._gardener(author) try: await self._apply_degradation(gardener) except discord.Forbidden: # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") if not gardener.current: message = "You're currently not growing a plant." em_color = discord.Color.red() else: plant = gardener.current degradation = await self._degradation(gardener) die_in = await _die_in(gardener, degradation) to_grow = await _grow_time(gardener) message = ( "You're growing {0} **{1}**. " "Its health is **{2:.2f}%** and still has to grow for **{3:.1f}** minutes. " "It is losing **{4:.2f}%** per minute and will die in **{5:.1f}** minutes.".format( plant["article"], plant["name"], plant["health"], to_grow, degradation.degradation, die_in, ) ) em_color = discord.Color.green() em = discord.Embed(description=message, color=em_color) await ctx.send(embed=em) @_gardening.command(name="buy") async def _buy(self, ctx, product=None, amount: int = 1): """Buy gardening supplies.""" if self.products is None: await self._load_plants_products() author = ctx.author if product is None: em = discord.Embed( title="All gardening supplies that you can buy:", color=discord.Color.green(), ) for pd in self.products: em.add_field( name="**{}**".format(pd.capitalize()), value="Cost: {} τ\n+{} health\n-{}% damage\nUses: {}\nCategory: {}".format( self.products[pd]["cost"], self.products[pd]["health"], self.products[pd]["damage"], self.products[pd]["uses"], self.products[pd]["category"], ), ) await ctx.send(embed=em) else: if amount <= 0: message = "Invalid amount! Must be greater than 1" else: gardener = await self._gardener(author) if product.lower() in self.products and amount > 0: cost = self.products[product.lower()]["cost"] * amount withdraw_points = await _withdraw_points(gardener, cost) if withdraw_points: if product.lower() not in gardener.products: gardener.products[product.lower()] = 0 # gardener.products[product.lower()] += amount # Only add it once gardener.products[product.lower()] += ( amount * self.products[product.lower()]["uses"] ) await gardener.save_gardener() message = "You bought {}.".format(product.lower()) else: message = "You don't have enough Thneeds. You have {}, but need {}.".format( gardener.points, self.products[product.lower()]["cost"] * amount, ) else: message = "I don't have this product." em = discord.Embed(description=message, color=discord.Color.green()) await ctx.send(embed=em) @_gardening.command(name="convert") async def _convert(self, ctx: commands.Context, amount: int): """Exchange Thneeds for credits.""" author = ctx.author gardener = await self._gardener(author) withdraw_points = await _withdraw_points(gardener, amount) plural = "" if amount > 0: plural = "s" if withdraw_points: await bank.deposit_credits(author, amount) message = "{} Thneed{} successfully exchanged for credits.".format(amount, plural) await gardener.save_gardener() else: message = "You don't have enough Thneed{}. " "You have {}, but need {}.".format( plural, gardener.points, amount ) em = discord.Embed(description=message, color=discord.Color.green()) await ctx.send(embed=em) @commands.command(name="shovel") async def _shovel(self, ctx: commands.Context): """Shovel your plant out.""" author = ctx.author gardener = await self._gardener(author) if not gardener.current: message = "You're currently not growing a plant." else: gardener.current = {} message = "You successfully shovelled your plant out." if gardener.points < 0: gardener.points = 0 await gardener.save_gardener() em = discord.Embed(description=message, color=discord.Color.dark_grey()) await ctx.send(embed=em) @commands.command(name="water") async def _water(self, ctx): """Water your plant.""" author = ctx.author channel = ctx.channel gardener = await self._gardener(author) try: await self._apply_degradation(gardener) except discord.Forbidden: # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") product = "water" product_category = "water" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: await self._add_health(channel, gardener, product, product_category) @commands.command(name="fertilize") async def _fertilize(self, ctx, fertilizer): """Fertilize the soil.""" gardener = await self._gardener(ctx.author) try: await self._apply_degradation(gardener) except discord.Forbidden: # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") channel = ctx.channel product = fertilizer product_category = "fertilizer" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: await self._add_health(channel, gardener, product, product_category) @commands.command(name="prune") async def _prune(self, ctx): """Prune your plant.""" gardener = await self._gardener(ctx.author) try: await self._apply_degradation(gardener) except discord.Forbidden: # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") channel = ctx.channel product = "pruner" product_category = "tool" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: await self._add_health(channel, gardener, product, product_category) # async def check_degradation(self): # while "PlantTycoon" in self.bot.cogs: # users = await self.config.all_users() # for user_id in users: # user = self.bot.get_user(user_id) # gardener = await self._gardener(user) # await self._apply_degradation(gardener) # await asyncio.sleep(self.defaults["timers"]["degradation"] * 60) async def _apply_degradation(self, gardener): if gardener.current: degradation = await self._degradation(gardener) now = int(time.time()) timestamp = gardener.current["timestamp"] degradation_count = (now - timestamp) // (self.defaults["timers"]["degradation"] * 60) degradation_count -= gardener.current["degrade_count"] gardener.current["health"] -= degradation.degradation * degradation_count gardener.points += self.defaults["points"]["growing"] * degradation_count gardener.current["degrade_count"] += degradation_count await gardener.save_gardener() await gardener.is_complete(now) async def check_completion_loop(self): while "PlantTycoon" in self.bot.cogs: now = int(time.time()) users = await self.config.all_users() for user_id in users: user = self.bot.get_user(user_id) if not user: continue gardener = await self._gardener(user) if not gardener: continue try: await self._apply_degradation(gardener) await gardener.is_complete(now) except discord.Forbidden: # Couldn't DM the results pass await asyncio.sleep(self.defaults["timers"]["completion"] * 60) async def send_notification(self): while "PlantTycoon" in self.bot.cogs: users = await self.config.all_users() for user_id in users: user = self.bot.get_user(user_id) if not user: continue gardener = await self._gardener(user) if not gardener: continue try: await self._apply_degradation(gardener) except discord.Forbidden: # Couldn't DM the degradation pass if gardener.current: health = gardener.current["health"] if health < self.defaults["notification"]["max_health"]: message = choice(self.notifications["messages"]) try: await user.send(message) except discord.Forbidden: # Couldn't DM the results pass await asyncio.sleep(self.defaults["timers"]["notification"] * 60) def __unload(self): self.completion_task.cancel() # self.degradation_task.cancel() self.notification_task.cancel()