diff --git a/cogs/cmd/farming.py b/cogs/cmd/farming.py new file mode 100644 index 0000000..653b688 --- /dev/null +++ b/cogs/cmd/farming.py @@ -0,0 +1,30 @@ +from discord.ext import commands + +from cogs.cmd.common import has_post_permission +from mbot import MiniscapeBotContext + + +class FarmingCommands: + @commands.group() + async def farm(self, ctx): + pass + + @farm.group(name="plant", aliases=["p"]) + @commands.check(has_post_permission) + async def _farm_plant(self, ctx: MiniscapeBotContext, *args): + ctx.bot.farm_manager.plant(ctx, args) + + @farm.group(name="harvest", aliases=["h"]) + @commands.check(has_post_permission) + async def _farm_harvest(self, ctx: MiniscapeBotContext, *args): + ctx.bot.farm_manager.harvest(ctx, args) + + @farm.group(name="check", aliases=["ch"]) + @commands.check(has_post_permission) + async def _farm_check(self, ctx: MiniscapeBotContext, *args): + ctx.bot.farm_manager.check(ctx, args) + + @farm.group(name="clear", aliases=["cl"]) + @commands.check(has_post_permission) + async def _farm_clear(self, ctx: MiniscapeBotContext, *args): + ctx.bot.farm_manager.check(ctx, args) diff --git a/cogs/cmd/items.py b/cogs/cmd/items.py index 4b481e6..1495e69 100644 --- a/cogs/cmd/items.py +++ b/cogs/cmd/items.py @@ -128,26 +128,3 @@ async def sell(self, ctx, *args): if number and item: out = item_helpers.sell(ctx.author.id, item, number=number) await ctx.send(out) - - #@commands.command() - async def sellall(self, ctx, maxvalue=None): - """Sells all items in the player's inventory (below a certain value) for gold pieces.""" - if has_post_permission(ctx.guild.id, ctx.channel.id): - name = get_display_name(ctx.author) - if maxvalue is not None: - value = users.get_value_of_inventory(ctx.author.id, under=maxvalue) - users.update_inventory(ctx.author.id, value*["0"]) - users.clear_inventory(ctx.author.id, under=maxvalue) - value_formatted = '{:,}'.format(value) - maxvalue_formatted = '{:,}'.format(users.parse_int(maxvalue)) - name = get_display_name(ctx.author) - out = f"All items in {name}'s inventory worth under {maxvalue_formatted} coins "\ - f"sold for {value_formatted} coins!" - else: - value = users.get_value_of_inventory(ctx.author.id) - users.update_inventory(ctx.author.id, value * ["0"]) - users.clear_inventory(ctx.author.id) - value_formatted = '{:,}'.format(value) - out = f"All items in {name}'s inventory "\ - f"sold for {value_formatted} coins!" - await ctx.send(out) diff --git a/cogs/cmd/user.py b/cogs/cmd/user.py index e679856..3145580 100644 --- a/cogs/cmd/user.py +++ b/cogs/cmd/user.py @@ -3,10 +3,12 @@ from discord.ext import commands from django.db.models import Q +import miniscape.models.user from cogs.cmd.common import get_display_name, has_post_permission from cogs.helper import items, users from cogs.errors.trade_error import TradeError +from mbot import MiniscapeBotContext from miniscape.models import User, Item from miniscape.itemconsts import FEDORA, COINS import miniscape.command_helpers as ch @@ -21,21 +23,21 @@ class UserCommands: This might be themselves (~me) or another user (~examine)""" @commands.group(invoke_without_command=True) - async def me(self, ctx): + async def me(self, ctx: MiniscapeBotContext): """Shows information related to the user.""" if has_post_permission(ctx.guild.id, ctx.channel.id): - await ctx.send(users.print_account(ctx.user_object)) + await ctx.send(ctx.user_object.print_account()) @me.group(name='stats', aliases=['levels']) - async def _me_stats(self, ctx): + async def _me_stats(self, ctx: MiniscapeBotContext): """Shows the levels and stats of a user.""" if has_post_permission(ctx.guild.id, ctx.channel.id): - await ctx.send(users.print_account(ctx.user_object, printequipment=False)) + await ctx.send(ctx.user_object.print_account(print_equipment=False)) @me.group(name='equipment', aliases=['armour', 'armor']) - async def _me_equipment(self, ctx): + async def _me_equipment(self, ctx: MiniscapeBotContext): if has_post_permission(ctx.guild.id, ctx.channel.id): - await ctx.send(users.print_equipment(ctx.user_object, with_header=True)) + await ctx.send(ctx.user_object.print_equipment(with_header=True)) @me.group(name='monsters') async def _me_monsters(self, ctx, *args): @@ -63,20 +65,24 @@ async def examine(self, ctx, *args): """Examines a given user.""" if has_post_permission(ctx.guild.id, ctx.channel.id): search_string = ' '.join(args).lower() - mems = await ctx.guild.query_members(query=search_string) + + # This is for loading the guild members. No clue whyt his works + # TODO: Fix and remove? + _ = await ctx.guild.query_members(query=search_string) + for member in ctx.guild.members: if member.nick is not None: if search_string in member.nick.lower(): - target = User.objects.get(id=member.id) + target: User = User.objects.get(id=member.id) break if search_string in member.name.lower(): - target = User.objects.get(id=member.id) + target: User = User.objects.get(id=member.id) break else: await ctx.send(f'Could not find {search_string} in server.') return - await ctx.send(users.print_account(target)) + await ctx.send(target.print_account()) @commands.command() async def tolevel(self, ctx, *args): @@ -88,7 +94,7 @@ async def tolevel(self, ctx, *args): else: level = None skill = ' '.join(args) - out = users.calc_xp_to_level(ctx.user_object, skill, level) + out = miniscape.models.user.calc_xp_to_level(ctx.user_object, skill, level) await ctx.send(out) @commands.command() diff --git a/cogs/helper/clues.py b/cogs/helper/clues.py deleted file mode 100755 index d5af6d0..0000000 --- a/cogs/helper/clues.py +++ /dev/null @@ -1,173 +0,0 @@ -import math -import random -import string -from collections import Counter - -from cogs.helper import items -from cogs.helper import monsters as mon -from cogs.helper import quests -from cogs.helper import users -from config import CLUES_DIRECTORY, CLUE_EMOJI - -DIFFICULTY = { - 1: 'easy', - 2: 'medium', - 3: 'hard', - 4: 'elite', - 5: 'master' -} - -CLUE_HEADER = f'{CLUE_EMOJI} __**CLUE SCROLL**__ {CLUE_EMOJI}\n' - -EASY_CLUE_SCROLL_ID = 184 -ROLLS_PER_CLUE = 5 - - -def calc_length(userid, difficulty): - """Calculates the time it takes to do a clue scroll.""" - quests_completed = len(users.get_completed_quests(userid)) - num_of_quests = len(list(quests.QUESTS.keys())) - player_damage = users.get_equipment_stats(users.read_user(userid, key=users.EQUIPMENT_KEY))[0] + 1 - - quest_multiplier = min((6 - difficulty) * quests_completed / num_of_quests, 1) - - base_time = 450 * difficulty - - time = base_time / (quest_multiplier * player_damage / 200) - - if time/base_time < 0.8: - time = 0.8 * base_time - return round(time) - - -def get_clue_scroll(person, *args): - try: - difficulty, length = args[0] - except ValueError as e: - print(e) - raise ValueError - - difficulty = int(difficulty) - loot = get_loot(difficulty) - users.update_inventory(person.id, loot) - - users.add_counter(person.id, str(difficulty), 1, key=users.CLUES_KEY) - out = f'{CLUE_HEADER}' \ - f'{person.mention}, you have finished your {DIFFICULTY[int(difficulty)]} clue scroll! ' \ - f'You have received the following items:\n' - out += print_loot(loot, difficulty) - return out - - -def get_loot(difficulty, factor=1): - """Generates a Counter of loot from a given clue scroll difficulty.""" - loot_table = get_loot_table(difficulty) - loot = [] - for _ in range(ROLLS_PER_CLUE): - for _ in range(round(5 * factor)): - item = random.sample(loot_table.keys(), 1)[0] - item_chance = loot_table[item]['rarity'] - if random.randint(1, item_chance) == 1 and int(item_chance) > 1: - item_min = loot_table[item]['min'] - item_max = loot_table[item]['max'] - for _ in range(random.randint(item_min, item_max)): - loot.append(item) - return Counter(loot) - - -def get_loot_table(difficulty): - """Creates and returns a dictionary of possible loot from a clue scroll.""" - loot_table = {} - with open(f'{CLUES_DIRECTORY}{str(difficulty)}.txt') as f: - items = f.read().splitlines() - for item in items: - itemid, itemmin, itemmax, rarity = item.split(';') - loot_table[itemid] = {} - loot_table[itemid]['min'] = int(itemmin) - loot_table[itemid]['max'] = int(itemmax) - loot_table[itemid]['rarity'] = int(rarity) - return loot_table - - -def get_rares(difficulty): - loottable = get_loot_table(difficulty) - - rares = [] - for item in list(loottable.keys()): - if int(loottable[item]['rarity']) > 256: - rares.append(item) - return rares - - -def print_item_from_lootable(item): - out = '' - for difficulty in range(1, 6): - loottable = get_loot_table(difficulty) - for itemid in list(loottable.keys()): - if int(itemid) == int(item): - name = f'{DIFFICULTY[difficulty]} clue scroll' - item_min = int(loottable[itemid]['min']) - item_max = int(loottable[itemid]['max']) - rarity = int(loottable[itemid]['rarity']) - - out += f'{string.capwords(name)} *(amount: ' - - if item_min == item_max: - out += f'{item_min}, ' - else: - out += f'{item_min}-{item_max}, ' - - for key in list(mon.RARITY_NAMES.keys()): - if key <= rarity: - name = key - else: - break - - out += f'rarity: {mon.RARITY_NAMES[name]})*\n' - return out - - -def print_loot(loot, difficulty): - """Converts a user's loot from a clue scroll to a string.""" - out = '' - rares = get_rares(difficulty) - for key in loot.keys(): - if key in rares: - out += f'**{loot[key]} {items.get_attr(key)}**\n' - else: - out += f'{loot[key]} {items.get_attr(key)}\n' - - total_value = '{:,}'.format(users.get_value_of_inventory(1234567890, inventory=loot)) - out += f'*Total value: {total_value}*' - - return out - - -def print_status(userid, time_left, *args): - """Prints a clue scroll and how long until it is finished.""" - difficulty, length = args[0] - out = f'{CLUE_HEADER}' \ - f'You are currently doing a {DIFFICULTY[int(difficulty)]} clue scroll for {length} minutes. ' \ - f'You will finish {time_left}. ' - return out - - -def start_clue(guildid, channelid, userid, difficulty): - """Starts a clue scroll.""" - from miniscape import adventures as adv - - out = f'{CLUE_HEADER}' - if not adv.is_on_adventure(userid): - scrollid = str(EASY_CLUE_SCROLL_ID + difficulty - 1) - if not users.item_in_inventory(userid, scrollid): - return f'Error: you do not have a {DIFFICULTY[difficulty]} clue scroll in your inventory.' - users.update_inventory(userid, [scrollid], remove=True) - - length = math.floor(calc_length(userid, difficulty) / 60) - clue = adv.format_line(4, userid, adv.get_finish_time(length * 60), guildid, channelid, difficulty, length) - adv.write(clue) - out += f'You are now doing a {DIFFICULTY[difficulty]} clue scroll for {length} minutes.' - else: - out = adv.print_adventure(userid) - out += adv.print_on_adventure_error('clue scroll') - return out diff --git a/cogs/helper/craft.py b/cogs/helper/craft.py index 0d00c45..4de2ee2 100755 --- a/cogs/helper/craft.py +++ b/cogs/helper/craft.py @@ -1,12 +1,5 @@ -import math -import random -import ujson from collections import Counter -from cogs.helper import items -from cogs.helper import prayer -from cogs.helper import users - ARTISAN_REQ_KEY = 'artisan' COOKING_REQ_KEY = 'cook' QUEST_REQ_KEY = 'quest req' diff --git a/cogs/helper/items.py b/cogs/helper/items.py index 1b6cabb..a7b9dd2 100755 --- a/cogs/helper/items.py +++ b/cogs/helper/items.py @@ -1,16 +1,7 @@ """This module contains methods that handle items and their attributes.""" -import random -import ujson -from collections import Counter -from cogs.helper import clues -from cogs.helper import monsters as mon -from cogs.helper import prayer -from cogs.helper import users -from config import ITEM_JSON, SHOP_FILE, ITEMS_EMOJI +from config import ITEMS_EMOJI -with open(ITEM_JSON, 'r', encoding='utf-8-sig') as f: - ITEMS = ujson.load(f) NAME_KEY = 'name' # Name of the item PLURAL_KEY = 'plural' # string containing how to pluralise an item's name. @@ -71,146 +62,4 @@ SHOP_HEADER = f'{ITEMS_EMOJI} __**SHOP**__ {ITEMS_EMOJI}\n' -def add_plural(number, itemid, with_zero=False): - if int(number) > 0: - out = f'{number} {get_attr(itemid)}' - else: - out = f'{get_attr(itemid)}' - if int(number) != 1: - suffix = get_attr(itemid, key=PLURAL_KEY) - for c in suffix: - out = out[:-1] if c == '_' else out + c - return out - - - - -def claim(userid, itemname, number): - try: - itemid = find_by_name(itemname) - except KeyError: - return f"Error: {itemname} is not an item." - - if not users.item_in_inventory(userid, itemid, number): - return f'You do not have {add_plural(number, itemid)} in your inventory.' - - out = ':moneybag: __**CLAIM**__ :moneybag:\n' - if itemid == "402": - out += 'You have received:\n' - gems = { - 25: 4, - 26: 16, - 27: 64, - 28: 128, - 463: 256, - 465: 512 - } - loot = [] - for _ in range(number): - while True: - gem_type = random.sample(gems.keys(), 1)[0] - if random.randint(1, gems[gem_type]) == 1: - loot.append(gem_type) - break - users.update_inventory(userid, loot) - loot_counter = Counter(loot) - for gemid in loot_counter.keys(): - out += f'{add_plural(loot_counter[gemid], gemid)}\n' - out += f'from your {add_plural(number, itemid)}.' - users.update_inventory(userid, number * [itemid], remove=True) - elif itemid == "370": - xp_per_effigy = 30000 - skills = Counter() - for _ in range(number): - skill = random.sample(users.SKILLS, 1)[0] - skills[skill] += 1 - users.update_inventory(userid, number * ['371']) - users.update_inventory(userid, number * [itemid], remove=True) - out += f"You have received the following xp from your {add_plural(number, '370')}!\n" - for skill in skills.keys(): - xp_gained = skills[skill] * xp_per_effigy - users.update_user(userid, xp_gained, key=skill) - xp_gained_formatted = '{:,}'.format(xp_gained) - out += f'{xp_gained_formatted} {skill} xp\n' - else: - out += f'{get_attr(itemid)} is not claimable.' - return out - - - - - - - -def find_by_name(name): - """Finds a item's ID from its name.""" - name = name.lower() - for itemid in list(ITEMS.keys()): - if name == ITEMS[itemid][NAME_KEY]: - return itemid - if name == add_plural(0, itemid): - return itemid - if any([name == nick for nick in get_attr(itemid, key=NICK_KEY)]): - return itemid - else: - raise KeyError - - -def get_attr(itemid, key=NAME_KEY): - """Gets an item's attribute from its id.""" - itemid = str(itemid) - if itemid in set(ITEMS.keys()): - try: - return ITEMS[itemid][key] - except KeyError: - ITEMS[itemid][key] = DEFAULT_ITEM[key] - return ITEMS[itemid][key] - else: - raise KeyError - - -def get_luck_factor(userid): - """Gets the luck factor of a user.""" - equipment = users.read_user(userid, key=users.EQUIPMENT_KEY) - luck_factor = 1 - for itemid in equipment.values(): - if int(itemid) > 0: - item_luck = get_attr(itemid, key=LUCK_KEY) - if item_luck > luck_factor: - luck_factor = item_luck - - user_prayer = users.read_user(userid, key=users.PRAY_KEY) - if user_prayer != -1: - prayer_factor = prayer.get_attr(user_prayer, key=prayer.FACTOR_KEY) - if prayer_factor > luck_factor: - luck_factor = prayer_factor - return luck_factor - - -def get_quest_shop_items(questid): - shop = open_shop() - quest_items = [] - for itemid in shop.keys(): - if shop[itemid] == questid: - quest_items.append(itemid) - return quest_items - - -def get_quest_items(questid): - quest_item = [] - for itemid in ITEMS.keys(): - if get_attr(itemid, key=QUEST_KEY) == questid: - quest_item.append(quest_item) - return quest_item - - -def is_tradable(itemid): - if get_attr(itemid, key=VALUE_KEY) < 1 or itemid in {'384', '267', '291'}: - return False - return True - - - - - diff --git a/cogs/helper/monsters.py b/cogs/helper/monsters.py index d797886..0511880 100755 --- a/cogs/helper/monsters.py +++ b/cogs/helper/monsters.py @@ -1,9 +1,9 @@ import random import string -import ujson from collections import Counter -from cogs.helper import items, users, quests +import ujson + from config import MONSTERS_JSON, MONSTER_DIRECTORY with open(MONSTERS_JSON, 'r') as f: diff --git a/cogs/helper/prayer.py b/cogs/helper/prayer.py index 17ef50d..b0dfa06 100755 --- a/cogs/helper/prayer.py +++ b/cogs/helper/prayer.py @@ -1,11 +1,3 @@ -import ujson - -from cogs.helper import users -from config import PRAYERS_JSON - -with open(PRAYERS_JSON, 'r') as f: - PRAYERS = ujson.load(f) - NAME_KEY = 'name' # Name of prayer, stored as a string. NICK_KEY = 'nick' # List of nicknames of prayer. DESC_KEY = 'description' # Description of function of prayer. diff --git a/cogs/helper/quests.py b/cogs/helper/quests.py deleted file mode 100755 index 11b7313..0000000 --- a/cogs/helper/quests.py +++ /dev/null @@ -1,50 +0,0 @@ -from collections import Counter - -import ujson - -from config import QUESTS_JSON, QUEST_EMOJI - -with open(QUESTS_JSON, 'r') as f: - QUESTS = ujson.load(f) - -NAME_KEY = 'name' -DESCRIPTION_KEY = 'description' -SUCCESS_KEY = 'success' -FAILURE_KEY = 'failure' -ITEM_REQ_KEY = 'item req' -QUEST_REQ_KEY = 'quest req' -REWARD_KEY = 'reward' -DAMAGE_KEY = 'damage' -ACCURACY_KEY = 'accuracy' -ARMOUR_KEY = 'armour' -LEVEL_KEY = 'level' -DRAGON_KEY = 'dragon' -TIME_KEY = 'time' - -DEFAULT_QUEST = { - NAME_KEY: 'Untitled Quest', - DESCRIPTION_KEY: 'Go something for this person and get some stuff in return.', - SUCCESS_KEY: 'You did it!', - FAILURE_KEY: "You didn't do it!", - ITEM_REQ_KEY: Counter(), - QUEST_REQ_KEY: [], - REWARD_KEY: Counter(), - DAMAGE_KEY: 1, - ACCURACY_KEY: 1, - ARMOUR_KEY: 1, - LEVEL_KEY: 1, - DRAGON_KEY: False, - TIME_KEY: 10 -} - -QUEST_HEADER = f'{QUEST_EMOJI} __**QUESTS**__ {QUEST_EMOJI}\n' - - - - - - - - - - diff --git a/cogs/helper/users.py b/cogs/helper/users.py index 1ef37e3..8983a6a 100755 --- a/cogs/helper/users.py +++ b/cogs/helper/users.py @@ -1,25 +1,9 @@ -import datetime -import string -import os -import shutil -import time -import ujson from collections import Counter -import operator - -import config -from cogs.helper import items -from cogs.helper import prayer -from cogs.helper import quests -from config import USER_DIRECTORY, BACKUP_DIRECTORY, XP_FILE, ARMOUR_SLOTS_FILE - -XP = {} -with open(XP_FILE, 'r') as f: - for line in f.read().splitlines(): - line_split = line.split(';') - XP[line_split[0]] = int(line_split[1]) +import ujson +import config +from config import USER_DIRECTORY IRONMAN_KEY = 'ironman' # User's ironman status, stored as a boolean. ITEMS_KEY = 'items' # User's inventory, stored as a Counter. @@ -108,176 +92,6 @@ LEADERBOARD_HEADER = f'$EMOJI __**$KEY LEADERBOARD**__ $EMOJI\n' -def add_counter(userid, value, number, key=MONSTERS_KEY): - """Adds a Counter to another Counter in a user's account.""" - new_counts = Counter({value: int(number)}) - try: - with open(f'{USER_DIRECTORY}{userid}.json', 'r') as f: - userjson = ujson.load(f) - counts = Counter(userjson[key]) - total_counts = counts + new_counts - userjson[key] = total_counts - except FileNotFoundError: - userjson = DEFAULT_ACCOUNT - userjson[MONSTERS_KEY] = new_counts - with open(f'{USER_DIRECTORY}{userid}.json', 'w+') as f: - ujson.dump(userjson, f) - - -def backup(): - """Backs up the user files.""" - current_time = int(time.time()) - destination = f'{BACKUP_DIRECTORY}{current_time}/' - os.makedirs(os.path.dirname(destination), exist_ok=True) - for file in os.listdir(USER_DIRECTORY): - shutil.copy(f'{USER_DIRECTORY}{file}', destination) - - -def calc_xp_to_level(author, skill, level): - """Calculates the xp needed to get to a level.""" - author_levels = author.skill_level_mapping - author_xps = author.skill_xp_mapping - - if skill not in author_levels.keys(): - return f'{skill} is not a skill.' - - if level is None: - level = author_levels[skill] + 1 - - if level > 99: - return f'You have already attained the maximum level in this skill.' - - current_xp = author_xps[skill] - for xp_value in XP.keys(): - if XP[xp_value] == level: - xp_needed = int(xp_value) - current_xp - break - else: - raise KeyError - xp_formatted = '{:,}'.format(xp_needed) - - out = f'You need {xp_formatted} xp to get level {level} in {skill}.' - return out - - -def clear_inventory(userid, under=None): - """Removes all items (with value) in an account's inventory.""" - if under is not None: - max_sell = int(under) - else: - max_sell = 2147483647 - inventory = read_user(userid) - locked_items = read_user(userid, key=LOCKED_ITEMS_KEY) - for itemid in inventory.keys(): - value = items.get_attr(itemid, key=items.VALUE_KEY) - if inventory[itemid] > 0 and 0 < value < max_sell and itemid not in locked_items: - inventory[itemid] = 0 - update_user(userid, inventory, key=ITEMS_KEY) - - - - - - - - -def count_item_in_inventory(userid, itemid): - """Gets the number of a given item from a user's inventory.""" - inventory = read_user(userid, key=ITEMS_KEY) - try: - number = int(inventory[str(itemid)]) - except KeyError: - number = 0 - return number - - -def get_total_level(userid): - """Gets the total level of a user.""" - total_level = 0 - for skill in SKILLS: - total_level += get_level(userid, key=skill) - return total_level - - -def get_values_by_account(key=ITEMS_KEY): - """Gets a certain value from all user's accounts and sorts them in descending order.""" - leaderboard = [] - for userfile in os.listdir(f'{USER_DIRECTORY}'): - userid = userfile[:-5] - - if key == ITEMS_KEY: - value = get_value_of_inventory(userid) - elif key == QUESTS_KEY: - value = len(get_completed_quests(userid)) - elif key == 'total': - value = get_total_level(userid) - else: - value = read_user(userid, key=key) - - if value > 0: - leaderboard.append((int(userid), int(value))) - return sorted(leaderboard, key=lambda x: x[1], reverse=True) - - -def get_completed_quests(userid): - hex_number = int(str(read_user(userid, key=QUESTS_KEY))[2:], 16) - binary_number = str(bin(hex_number))[2:] - completed_quests = [] - for bit in range(len(binary_number)): - if binary_number[bit] == '1': - completed_quests.append(len(binary_number) - bit) - return completed_quests - - -def get_equipment_stats(equipment, userid=None): - """Gets the total combat stats for the current equipment worn by a user.""" - damage = 0 - accuracy = 0 - armour = 0 - prayer_bonus = 0 - for itemid in equipment.values(): - try: - damage += items.get_attr(itemid, key=items.DAMAGE_KEY) - accuracy += items.get_attr(itemid, key=items.ACCURACY_KEY) - armour += items.get_attr(itemid, key=items.ARMOUR_KEY) - prayer_bonus += items.get_attr(itemid, key=items.PRAYER_KEY) - except KeyError: - pass - - if userid is not None: - user_prayer = read_user(userid, key=PRAY_KEY) - damage *= prayer.get_attr(user_prayer, key=prayer.DAMAGE_KEY) / 100 - accuracy *= prayer.get_attr(user_prayer, key=prayer.ACCURACY_KEY) / 100 - armour *= prayer.get_attr(user_prayer, key=prayer.ARMOUR_KEY) / 100 - - return damage, accuracy, armour, prayer_bonus - - -def get_level(userid, key): - """Gets a user's skill level from their userid.""" - xp = read_user(userid, key=key) - return xp_to_level(xp) - - -def get_value_of_inventory(userid, inventory=None, under=None, add_locked=False): - """Gets the total value of a user's inventory.""" - if inventory is None: - inventory = read_user(userid) - if under is not None: - max_value = int(under) - else: - max_value = 999999999999 - - total_value = 0 - locked_items = set(read_user(userid, key=LOCKED_ITEMS_KEY)) - for item in list(inventory.keys()): - value = items.get_attr(item, key=items.VALUE_KEY) - if value < max_value: - if item not in locked_items or add_locked: - total_value += int(inventory[item]) * value - return total_value - - def item_in_inventory(userid, item, number=1): """Determines whether (a given number of) an item is in a user's inventory.""" with open(f'{USER_DIRECTORY}{userid}.json', 'r+') as f: @@ -293,40 +107,6 @@ def item_in_inventory(userid, item, number=1): return False -def lock_item(userid, item): - """Locks an item from being sold accidentally.""" - try: - itemid = items.find_by_name(item) - except KeyError: - return f'No item with name {item} found.' - - item_name = items.get_attr(itemid) - locked_items = read_user(userid, key=LOCKED_ITEMS_KEY) - if itemid in locked_items: - return f'{item_name} is already locked.' - locked_items.append(itemid) - update_user(userid, locked_items, key=LOCKED_ITEMS_KEY) - - return f'{item_name} has been locked!' - - -def unlock_item(userid, item): - """Unlocks an item, allowing it to be sold again.""" - try: - itemid = items.find_by_name(item) - except KeyError: - return f'No item with name {item} found.' - - item_name = items.get_attr(itemid) - locked_items = read_user(userid, key=LOCKED_ITEMS_KEY) - if itemid not in locked_items: - return f'{item_name} is already unlocked.' - locked_items.remove(itemid) - update_user(userid, locked_items, key=LOCKED_ITEMS_KEY) - - return f'{item_name} has been unlocked!' - - def parse_int(number_as_string): """Converts an string into an int if the string represents a valid integer""" try: @@ -354,67 +134,6 @@ def parse_int(number_as_string): raise ValueError -def print_account(author, printequipment=True): - """Writes a string showing basic user information.""" - nickname = author.nick if author.nick else author.name - out = f"{CHARACTER_HEADER.replace('$NAME', nickname.upper())}" - - # TODO: This can probably be done better - for skill, level, skill_name in zip(author.xp_fields_str, author.level_fields_str, SKILLS): - xp_formatted = '{:,}'.format(getattr(author, skill, 0)) - out += f'**{string.capwords(skill_name)} Level**: {getattr(author, level, 0)} *({xp_formatted} xp)*\n' - - out += f'**Skill Total**: {author.total_level}/{len(SKILLS) * 99}\n\n' - out += f'**Quests Completed**: {len(author.completed_quests.all())}/{len(quests.QUESTS.keys())}\n\n' - - if printequipment: - out += print_equipment(author) - - return out - - -def print_equipment(author, name=None, with_header=False): - """Writes a string showing the stats of a user's equipment.""" - - armour_print_order = ['Head', 'Back', 'Neck', 'Ammunition', 'Main-Hand', 'Torso', 'Off-Hand', - 'Legs', 'Hands', 'Feet', 'Ring', 'Pocket', 'Hatchet', 'Pickaxe', 'Potion'] - - if with_header and name is not None: - out = f"{CHARACTER_HEADER.replace('$NAME', name.upper())}" - else: - out = '' - equipment = author.all_armour - damage, accuracy, armour, prayer = author.equipment_stats - out += f'**Damage**: {damage}\n' \ - f'**Accuracy**: {accuracy}\n' \ - f'**Armour**: {armour}\n' \ - f'**Prayer Bonus**: {prayer}\n' - - if author.prayer_slot: - out += f'**Active Prayer**: {author.prayer_slot.name}\n' - else: - out += f'**Active Prayer**: none\n' - - if author.active_food: - out += f'**Active Food**: {author.active_food.name}\n\n' - else: - out += f'**Active Food**: none\n\n' - - for slot in armour_print_order: - item = equipment[slot] - out += f'**{string.capwords(slot)}**: ' - if item is not None: - out += f'{item.name} ' - out += f'*(dam: {item.damage}, ' \ - f'acc: {item.accuracy}, ' \ - f'arm: {item.armour}, ' \ - f'pray: {item.prayer})*\n' - else: - out += 'none *(dam: 0, acc: 0, arm: 0, pray: 0)*\n' - - return out - - def read_user_multi(*args, **kwargs): """Reads the value of the same key across multiple users""" return [read_user(u, key=kwargs['key']) for u in args] @@ -445,32 +164,6 @@ def read_user(userid, key=ITEMS_KEY): return userjson[key] -def remove_potion(userid): - """Removes a potion from a player's equipment.""" - equipment = read_user(userid, key=EQUIPMENT_KEY) - equipment['15'] = -1 - update_user(userid, equipment, key=EQUIPMENT_KEY) - - -def reset_account(userid): - """Sets a user's keys to the DEFAULT_ACCOUNT.""" - userjson = DEFAULT_ACCOUNT - with open(f'{USER_DIRECTORY}{userid}.json', 'w+') as f: - ujson.dump(userjson, f) - - -def reset_dailies(): - """Resets the completion of all users' dailies.""" - for userid in os.listdir(USER_DIRECTORY): - print(userid[-4:]) - if userid[-4:] == 'json': - print(userid[:-5]) - userid = userid[:-5] - update_user(userid, False, key=VIS_KEY) - update_user(userid, 0, key=VIS_ATTEMPTS_KEY) - update_user(userid, False, key=REAPER_KEY) - - def update_inventory(userid, loot, remove=False): """Adds or removes items from a user's inventory.""" try: @@ -513,11 +206,3 @@ def update_user(userid, value, key=ITEMS_KEY): ujson.dump(userjson, f) -def xp_to_level(xp): - """Converts a user's xp into its equivalent level based on an XP table.""" - xp = int(xp) - for level_xp in XP: - if int(level_xp) > xp: - return int(XP[level_xp]) - 1 - else: - return 99 diff --git a/cogs/helper/vis.py b/cogs/helper/vis.py index 6f82ff1..156619d 100755 --- a/cogs/helper/vis.py +++ b/cogs/helper/vis.py @@ -1,8 +1,5 @@ -import random -import time - +from cogs.helper import items from config import RUNEID_FILE, VIS_FILE, VIS_SHOP_FILE -from cogs.helper import items, users # The way ~vis works is that there are three slots for runes to fit. Each are randomly selected each day. # @@ -21,133 +18,5 @@ # * 7: third of three second choices of second slot; same for everyone. # * 8: the unix timestamp for the current date. -RUNEIDS = {} -with open(RUNEID_FILE, 'r') as f: - for line in f.read().splitlines(): - line_split = line.split(';') - RUNEIDS[line_split[0]] = line_split[1] - -VIS_SHOP = {} -with open(VIS_SHOP_FILE, 'r') as f: - for line in f.read().splitlines(): - line_split = line.split(';') - VIS_SHOP[line_split[0]] = {} - VIS_SHOP[line_split[0]]['number'] = int(line_split[1]) - VIS_SHOP[line_split[0]]['cost'] = int(line_split[2]) - BASE_COST = 2500 VIS_DELTA = 250 - - -def calc(userid, items_input): - """Calculates the number of vis wax given the userid and a list of items. - Returns a list of length 3 representing how many vis wax they have received in each slot.""" - runes = [] - for item in items_input: - try: - itemid = items.find_by_name(item) - except KeyError: - return f"Cannot find item {item}." - try: - for runeid in RUNEIDS.keys(): - if RUNEIDS[runeid] == itemid: - runes.append(runeid) - except KeyError: - return f"{items.get_attr(itemid)} is not a rune." - - vis = open_vis() - num_vis = [0, 0, 0] - # print(f"{runes[0]} {vis[0]}") - if runes[0] == vis[0]: - num_vis[0] += 40 - elif runes[0] == vis[1]: - num_vis[0] += 20 - else: - num_vis[0] += 10 - - if runes[1] == vis[2 + 2 * (userid % 2)]: - num_vis[1] += 30 - elif runes[1] == vis[3 + 2 * (userid % 2)]: - num_vis[1] += 15 - else: - num_vis[1] += 7 - - if runes[2] == calc_third_rune(userid, timestamp=vis[8]): - num_vis[2] += 30 - elif runes[2] == calc_third_rune(userid, timestamp=vis[8], best=False): - num_vis[2] += 15 - else: - num_vis[2] += 7 - - return num_vis - - -def calc_num(attempts): - """Calculates the number of runes required to produce vis wax given a number of attempts.""" - # A given attempt will increase the change in the number of runes needed by VIS_DELTA. That is to say: - # dy/dx = VIS_DELTA * x. Thus integrating, we get y = (VIS_DELTA / 2) * x^2 + BASE_COST. - return round((VIS_DELTA / 2) * ((attempts - 1) ** 2) + BASE_COST) - - -def calc_third_rune(userid, timestamp=None, best=True): - if timestamp is None: - timestamp = open_vis()[8] - timestamp = int(timestamp) - return str(round((userid % timestamp) % len(RUNEIDS.keys()))) if best else\ - str(round((userid % timestamp + round(timestamp / 50)) % len(RUNEIDS.keys()))) - - -def open_vis(): - """Opens the vis wax list.""" - with open(VIS_FILE, 'r') as f: - vis = f.read().splitlines() - return vis - - -def shop_print(): - """Prints the vis wax shop.""" - out = f'{items.SHOP_HEADER}' - for itemid in VIS_SHOP.keys(): - if VIS_SHOP[itemid]['number'] > 1: - out += f"{items.add_plural(VIS_SHOP[itemid]['number'], itemid)}: " - else: - out += f'{items.get_attr(itemid)}: ' - out += f"{VIS_SHOP[itemid]['cost']} vis wax\n" - return out - - -def shop_buy(userid, item, number=1): - """Buys an item from the vis wax shop.""" - try: - itemid = items.find_by_name(item) - except KeyError: - return f"{item} not found." - - item_name = items.get_attr(itemid) - try: - vis_cost = VIS_SHOP[itemid]['cost'] - except KeyError: - return f'{item_name} not in vis shop.' - - if not users.item_in_inventory(userid, '579', number * vis_cost): - return f'You do not have enough vis wax to purchase this many {item_name}' - vis_num = VIS_SHOP[itemid]['number'] - users.update_inventory(userid, number * vis_cost * ['579'], remove=True) - users.update_inventory(userid, number * vis_num * [itemid]) - return f'You have purchased {items.add_plural(number, itemid)} for {number * vis_cost} vis wax!' - - -def update_vis(): - """Changes the daily runes for vis wax.""" - vis = [] - for _ in range(8): - vis.append(random.sample(RUNEIDS.keys(), k=1)[0]) - vis.append(str(int(time.time()))) - with open(VIS_FILE, 'w') as f: - f.write('\n'.join(vis)) - - -if __name__ == '__main__': - userid = 12345678 - print(calc_third_rune(userid)) - print(calc_third_rune(userid, timestamp=open_vis()[8])) diff --git a/cogs/managers/farming_manager.py b/cogs/managers/farming_manager.py new file mode 100644 index 0000000..872cc7c --- /dev/null +++ b/cogs/managers/farming_manager.py @@ -0,0 +1,12 @@ + + +class FarmingManager: + + def plant(self, ctx, args): + pass + + def harvest(self, ctx, args): + pass + + def check(self, ctx, args): + pass diff --git a/mbot.py b/mbot.py index 764ec08..e07e7df 100755 --- a/mbot.py +++ b/mbot.py @@ -1,21 +1,32 @@ #! /usr/bin/env python3 """Runs bots for a Discord server.""" # These lines allow us to use Django models -import logging import os -import sys -import asyncio -import traceback os.environ.setdefault("DJANGO_SETTINGS_MODULE", "miniscapebot.settings") import django django.setup() + +<<<<<<< Updated upstream +import logging +import sys +import asyncio +import traceback +======= +import sys +import asyncio +import traceback +import logging + +>>>>>>> Stashed changes from django.core.exceptions import ObjectDoesNotExist +from cogs.managers.farming_manager import FarmingManager from discord.ext import commands import cogs.managers.trade_manager as tm from miniscape.models import User import config from pythonjsonlogger import jsonlogger + def extensions_generator(): """Returns a generator for all cog files that aren't in do_not_use.""" cog_path = "./cogs" @@ -24,15 +35,19 @@ def extensions_generator(): if cog not in do_not_use: yield f"cogs.{cog[:-3]}" + DESCRIPTION = "A bot that runs a basic role-playing game." + # log = logging.getLogger(__name__) class MiniscapeBot(commands.Bot): """Defines the miniscapebot class and functions.""" def __init__(self): - super().__init__(command_prefix=["~", "%", "./"], description=DESCRIPTION) + super().__init__(command_prefix=["~", "%", "./", "."], description=DESCRIPTION) + self.trade_manager = None + self.farm_manager = None self.default_nick = "Miniscape" self.add_command(self.load) self.remove_command('help') @@ -61,6 +76,7 @@ def setup_logging(self): def initialize_managers(self): self.trade_manager = tm.TradeManager() + self.farm_manager = FarmingManager() async def on_ready(self): """Prints bot initialization info""" @@ -110,9 +126,11 @@ async def load(self, ctx, extension): await ctx.send(f'Failed to load extension {extension}.', file=sys.stderr) traceback.print_exc() + class MiniscapeBotContext(commands.Context): def __init__(self, *args, **kwargs): + self.bot: MiniscapeBot = None super().__init__(*args, **kwargs) try: @@ -134,4 +152,3 @@ def __init__(self, *args, **kwargs): self.user_object.nick = self.author.nick if self.author.nick is not None else '' self.user_object.name = self.author.name + '#' + self.author.discriminator self.user_object.save() - diff --git a/miniscape/adventures.py b/miniscape/adventures.py index 9d14a39..1d5634a 100755 --- a/miniscape/adventures.py +++ b/miniscape/adventures.py @@ -2,7 +2,7 @@ import math import random -from cogs.helper import quests, slayer, craft, clues +from cogs.helper import slayer, craft from config import ADVENTURES_FILE from miniscape import slayer_helpers as sh, clue_helpers, quest_helpers, craft_helpers diff --git a/miniscape/craft_helpers.py b/miniscape/craft_helpers.py index 9574c25..2f738dd 100755 --- a/miniscape/craft_helpers.py +++ b/miniscape/craft_helpers.py @@ -35,7 +35,7 @@ def start_gather(guildid, channelid, user: User, itemname, length=-1, number=-1) item: Item = Item.find_by_name_or_nick(itemname) if not item: - return f'Error: {item} is not an item.' + return f'Error: {itemname} is not an item.' try: length = int(length) @@ -47,7 +47,7 @@ def start_gather(guildid, channelid, user: User, itemname, length=-1, number=-1) return f'Error: you cannot gather item {item.name}.' quest_req = item.quest_req - if quest_req and quest_req not in user.completed_quests_list: + if quest_req and quest_req not in list(user.completed_quest_queryset): return f'Error: You do not have the required quest to gather this item.' item_name = item.name @@ -151,7 +151,7 @@ def print_list(user: User, search, filter_quests=True, allow_empty=True): recipes = list(recipes) if not recipes and not allow_empty: return [] - user_quests = user.completed_quests_list + user_quests = list(user.completed_quest_queryset) for recipe in recipes: msg = f'**{string.capwords(recipe.creates.name)}** *(level {recipe.level_requirement})*\n' @@ -183,7 +183,8 @@ def get_runecraft(person, *args): except ValueError as e: print(e) raise ValueError - user = User.objects.get(id=person.id) + + user = User.objects.get(id=person) item = Item.objects.get(id=itemid) if not user.has_item_amount_by_item(RUNE_ESSENCE, number) \ @@ -454,34 +455,42 @@ def start_runecraft(guildid, channelid, user: User, item, number=1, pure=0): if not item: return f'{item} is not an item.' + if not item.is_rune: + return f'{item.name} is not a rune that can be crafted.' + try: number = int(number) except ValueError: return f'{number} is not a valid number.' +<<<<<<< Updated upstream if not item.is_rune: - return f'{items.get_attr(itemid)} is not a rune that can be crafted.' + return f'{item.name} is not a rune that can be crafted.' # Find out if user has the talisman rune_type = item.name.split(" ")[0] if not user.has_item_by_name(rune_type + " talisman"): - return f'{items.get_attr(talismanid)} not found in inventory.' + return f'{rune_type + " talisman"} not found in inventory.' +======= + # Find out if user has the talisman + rune_type = item.name.split(" ")[0] + if not user.has_item_by_name(rune_type + " talisman"): + return f'Appropriate talisman not found in inventory.' +>>>>>>> Stashed changes - item_name = item.name - runecrafting_level = user.rc_level runecraft_req = item.level player_potion = user.potion_slot.id if user.potion_slot else '0' if player_potion == 435: - boosted_level = runecrafting_level + 3 + boosted_level = user.rc_level + 3 elif player_potion == 436: - boosted_level = runecrafting_level + 6 + boosted_level = user.rc_level + 6 else: - boosted_level = runecrafting_level + boosted_level = user.rc_level if boosted_level < runecraft_req: - return f'Error: {item_name} has a runecrafting requirement ({runecraft_req}) higher ' \ - f'than your runecrafting level ({runecrafting_level})' + return f'Error: {item.name} has a runecrafting requirement ({runecraft_req}) higher ' \ + f'than your runecrafting level ({user.rc_level})' if item.quest_req and not user.has_completed_quest(item.quest_req): return f'You do not have the required quest to craft this rune.' @@ -500,7 +509,7 @@ def start_runecraft(guildid, channelid, user: User, item, number=1, pure=0): return f'You do not have enough essence to craft this many runes.' rc_session = adv.format_line(6, user.id, adv.get_finish_time(length * 60), guildid, channelid, - item.id, item_name, number, length, pure) + item.id, item.name, number, length, pure) adv.write(rc_session) out += f'You are now crafting {item.pluralize(number)} for {length} minutes.' else: diff --git a/miniscape/models/__init__.py b/miniscape/models/__init__.py index 11a0e00..ba6f6ab 100755 --- a/miniscape/models/__init__.py +++ b/miniscape/models/__init__.py @@ -8,3 +8,4 @@ from .playermonsterkills import PlayerMonsterKills from .clueloot import ClueLoot from .periodicchecker import PeriodicChecker +from .farming_plot import FarmPlot diff --git a/miniscape/models/farming_plot.py b/miniscape/models/farming_plot.py new file mode 100644 index 0000000..daf3dd5 --- /dev/null +++ b/miniscape/models/farming_plot.py @@ -0,0 +1,67 @@ +from django.db import models +from django.utils import timezone + + +class Patch: + def __init__(self, name, identifier): + self.identifier: int = identifier + self.name: str = name + + +ALLOTMENT_PATCH = Patch("allotment patch", 1) +HERB_PATCH = Patch("herb patch", 2) +TREE_PATCH = Patch("tree patch", 3) +FRUIT_TREE_PATCH = Patch("fruit tree patch", 4) +BUSH_PATCH = Patch("bush patch", 5) +FLOWER_PATCH = Patch("flower patch", 6) + +type_choices = ( + (ALLOTMENT_PATCH.identifier, ALLOTMENT_PATCH.name), + (HERB_PATCH.identifier, HERB_PATCH.name), + (TREE_PATCH.identifier, TREE_PATCH.name), + (FRUIT_TREE_PATCH.identifier, FRUIT_TREE_PATCH.name), + (BUSH_PATCH.identifier, BUSH_PATCH.name), + (FLOWER_PATCH.identifier, FLOWER_PATCH.name) +) + + +class FarmPlot(models.Model): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + id = models.PositiveIntegerField(primary_key=True) + name = models.CharField(max_length=200, + blank=True, + default="") + nick = models.CharField(max_length=200, + blank=True, + default="", + null=True) + + type = models.PositiveIntegerField(choices=type_choices, + default=ALLOTMENT_PATCH.identifier) + + quest_req = models.ForeignKey('Quest', + on_delete=models.SET_NULL, + null=True) + + def __repr__(self): + return f'FarmPlot "{self.nick}"; ID: {self.id}; type: {type_choices[self.type][1]}' + + def __str__(self): + return self.__repr__() + + +class UserFarmPlot(models.Model): + class Meta: + unique_together = (('user', 'plot'),) + + plot = models.ForeignKey('FarmPlot', + on_delete=models.CASCADE) + user = models.ForeignKey('User', + on_delete=models.CASCADE) + seed = models.ForeignKey('Item', + on_delete=models.CASCADE) + time_planted = models.DateTimeField(auto_now_add=True) + time_last_harvested = models.DateTimeField(default=timezone.now) diff --git a/miniscape/models/item.py b/miniscape/models/item.py index dba20cd..a61abd6 100755 --- a/miniscape/models/item.py +++ b/miniscape/models/item.py @@ -32,6 +32,9 @@ class Item(models.Model): is_max_only = models.BooleanField(default=False) is_pet = models.BooleanField(default=False) + # Farming + farm_patch = models.PositiveIntegerField(default=0) + # misc pouch = models.PositiveIntegerField(default=0) luck_modifier = models.FloatField(default=0) diff --git a/miniscape/models/monster.py b/miniscape/models/monster.py index 4cb569c..07e8641 100755 --- a/miniscape/models/monster.py +++ b/miniscape/models/monster.py @@ -7,7 +7,6 @@ class Monster(models.Model): - id = models.PositiveIntegerField(primary_key=True) name = models.CharField(max_length=200, unique=True) @@ -16,8 +15,6 @@ class Monster(models.Model): default="s") nick = models.ManyToManyField('MonsterNickname') - - # Stats xp = models.PositiveIntegerField(default=0) slayer_level_req = models.PositiveIntegerField(default=0) diff --git a/miniscape/models/recipe.py b/miniscape/models/recipe.py index a7631bd..d2e7a35 100755 --- a/miniscape/models/recipe.py +++ b/miniscape/models/recipe.py @@ -1,9 +1,7 @@ - from django.db import models class Recipe(models.Model): - creates = models.ForeignKey('Item', related_name="item_creates", on_delete=models.CASCADE) @@ -28,6 +26,38 @@ def __str__(self): return self.__repr__() +class FarmRecipe(models.Model): + creates = models.ForeignKey('Item', + related_name="item_creates", + on_delete=models.CASCADE) + level_requirement = models.PositiveIntegerField(default=0) + seed_requirement = models.ForeignKey('Item', + related_name="item_creates", + on_delete=models.CASCADE) + # How long it takes the plant to grow + growth_time_minutes = models.PositiveIntegerField(null=False, + default=60) + + # If it's a wood tree, it doesn't give fruit or deplete. It gives a certain number of logs + log_yield = models.PositiveIntegerField(null=False, + default=0) + + # How long it takes the individual fruits to regrow. 0 means it doesn't regrow + fruit_growth_time_minutes = models.PositiveIntegerField(null=False, + default=0) + + # Maximum number of fruits that can be on the tree or bush. Only relevant if fruit_growth_time_minutes is >0 + max_fruits = models.PositiveIntegerField(null=False, + default=60) + + # Depletion chance specifies the odds of a "depletion" being consumed every pick. A lower depletion chance or higher + # number of depletions means a bigger harvest. If set 0, means it's a fruit bearing plant and uses static numbers + depletion_chance = models.PositiveIntegerField(null=False, + default=0) + depletions = models.PositiveIntegerField(null=False, + default=0) + + class RecipeRequirement(models.Model): class Meta: unique_together = (('recipe', 'item'),) diff --git a/miniscape/models/user.py b/miniscape/models/user.py index 1a82133..e748021 100755 --- a/miniscape/models/user.py +++ b/miniscape/models/user.py @@ -1,15 +1,128 @@ +import string from collections import Counter from django.db import models +from django.db.models import Q +import config from .quest import Quest from .prayer import Prayer from .monster import Monster from .userinventory import UserInventory from .item import Item, ItemNickname -from django.core.exceptions import ObjectDoesNotExist from .playermonsterkills import PlayerMonsterKills -from cogs.helper.users import xp_to_level +from .farming_plot import FarmPlot + +CHARACTER_HEADER = f'{config.COMBAT_EMOJI} __**$NAME**__ {config.COMBAT_EMOJI}\n' + +COMBAT_XP_KEY = 'combat' # User's combat xp, stored as an int. +SLAYER_XP_KEY = 'slayer' # User's slayer xp, stored as an int. +GATHER_XP_KEY = 'gather' # User's gathering xp, stored as an int. +ARTISAN_XP_KEY = 'artisan' # User's artisan xp, stored as an int. +COOK_XP_KEY = 'cook' # User's cooking xp, stored as an int. +PRAY_XP_KEY = 'prayer' # User's prayer xp, stored as an int. +RC_XP_KEY = 'runecrafting' # User's runecrafting xp, stored as an int. +SKILLS = [COMBAT_XP_KEY, SLAYER_XP_KEY, GATHER_XP_KEY, ARTISAN_XP_KEY, COOK_XP_KEY, PRAY_XP_KEY, RC_XP_KEY] + +XP = {'0': 1, + '81': 2, + '174': 3, + '276': 4, + '388': 5, + '512': 6, + '650': 7, + '801': 8, + '969': 9, + '1154': 10, + '1358': 11, + '1583': 12, + '1832': 13, + '2107': 14, + '2411': 15, + '2746': 16, + '3115': 17, + '3523': 18, + '3973': 19, + '4470': 20, + '5018': 21, + '5624': 22, + '6291': 23, + '7028': 24, + '7842': 25, + '8740': 26, + '9730': 27, + '10824': 28, + '12031': 29, + '13363': 30, + '14833': 31, + '16456': 32, + '18247': 33, + '20224': 34, + '22406': 35, + '24815': 36, + '27473': 37, + '30408': 38, + '33648': 39, + '37224': 40, + '41171': 41, + '45529': 42, + '50339': 43, + '55649': 44, + '61512': 45, + '67983': 46, + '75127': 47, + '83014': 48, + '91721': 49, + '101333': 50, + '111945': 51, + '123660': 52, + '136593': 53, + '150872': 54, + '166636': 55, + '184039': 56, + '203253': 57, + '224466': 58, + '247885': 59, + '273741': 60, + '302287': 61, + '333803': 62, + '368599': 63, + '407014': 64, + '449427': 65, + '496253': 66, + '547952': 67, + '605031': 68, + '668050': 69, + '737627': 70, + '814444': 71, + '899256': 72, + '992894': 73, + '1096277': 74, + '1210420': 75, + '1336442': 76, + '1475580': 77, + '1629199': 78, + '1798807': 79, + '1986067': 80, + '2192817': 81, + '2421086': 82, + '2673113': 83, + '2951372': 84, + '3258593': 85, + '3597791': 86, + '3972293': 87, + '4385775': 88, + '4842294': 89, + '5346331': 90, + '5902830': 91, + '6517252': 92, + '7195628': 93, + '7944613': 94, + '8771557': 95, + '9684576': 96, + '10692628': 97, + '11805605': 98, + '13034430': 99} class User(models.Model): @@ -35,7 +148,7 @@ def __init__(self, *args, **kwargs): 'cook': self.cook_level, 'cooking': self.cook_level, 'pray': self.prayer_level, - 'prayer': self.prayer_level, + 'prayer': self.prayer_level, 'rc': self.rc_level, 'runecrafting': self.rc_level} @@ -65,7 +178,7 @@ def __init__(self, *args, **kwargs): 'cook': self.cook_xp, 'cooking': self.cook_xp, 'pray': self.prayer_xp, - 'prayer': self.prayer_xp, + 'prayer': self.prayer_xp, 'rc': self.rc_xp, 'runecrafting': self.rc_xp} @@ -290,7 +403,7 @@ def update_inventory(self, loot, amount=1, remove=False): self._add_inventory_object(item, amount=amount) self.save() - def _update_inventory_item_id(self, loot: str, amount=1, remove=False): + def _update_inventory_item_id(self, loot: str, amount=1, remove=False): item = Item.objects.get(id=loot) return self._update_inventory_object(item, amount=amount, remove=remove) @@ -338,8 +451,8 @@ def unlock_item(self, itemname): def monster_kills(self, search=None): if search: - return self.playermonsterkills_set.\ - filter(monster__name__icontains=search)\ + return self.playermonsterkills_set. \ + filter(monster__name__icontains=search) \ .order_by('monster__name') else: return self.playermonsterkills_set.all().order_by('monster__name') @@ -354,7 +467,7 @@ def can_use_prayer(self, prayer: Prayer): # Validate prayer level if self.prayer_level >= prayer.level_required: # Validate quest req - if not prayer.quest_req or prayer.quest_req in self.completed_quests_list: + if not prayer.quest_req or prayer.quest_req in list(self.completed_quest_queryset): return True # Default @@ -448,36 +561,96 @@ def has_quest_req_for_quest(self, quest: Quest, cached=None): return True def has_completed_quest(self, quest): - return quest in self.completed_quests_list + return quest in list(self.completed_quest_queryset) def has_items_for_quest(self, quest: Quest): quest_items = quest.required_items if quest_items: - quest_items = {qir.item : qir.amount for qir in quest_items} + quest_items = {qir.item: qir.amount for qir in quest_items} quest_items = Counter(quest_items) return self.has_item_amount_by_counter(quest_items) else: return True + def print_account(self, printequipment=True): + """Writes a string showing basic user information.""" + nickname = self.nick if self.nick else self.name + out = f"{CHARACTER_HEADER.replace('$NAME', nickname.upper())}" + + # TODO: This can probably be done better + for skill, level, skill_name in zip(self.xp_fields_str, self.level_fields_str, SKILLS): + xp_formatted = '{:,}'.format(getattr(self, skill, 0)) + out += f'**{string.capwords(skill_name)} Level**: {getattr(self, level, 0)} *({xp_formatted} xp)*\n' + + out += f'**Skill Total**: {self.total_level}/{len(SKILLS) * 99}\n\n' + out += f'**Quests Completed**: {len(self.completed_quests.all())}/{len(Quest.objects.all())}\n\n' + + if printequipment: + out += self.print_equipment() + + return out + + def print_equipment(self, name=None, with_header=False): + """Writes a string showing the stats of a user's equipment.""" + + armour_print_order = ['Head', 'Back', 'Neck', 'Ammunition', 'Main-Hand', 'Torso', 'Off-Hand', + 'Legs', 'Hands', 'Feet', 'Ring', 'Pocket', 'Hatchet', 'Pickaxe', 'Potion'] + + if with_header and name is not None: + out = f"{CHARACTER_HEADER.replace('$NAME', name.upper())}" + else: + out = '' + equipment = self.all_armour + damage, accuracy, armour, prayer = self.equipment_stats + out += f'**Damage**: {damage}\n' \ + f'**Accuracy**: {accuracy}\n' \ + f'**Armour**: {armour}\n' \ + f'**Prayer Bonus**: {prayer}\n' + + if self.prayer_slot: + out += f'**Active Prayer**: {self.prayer_slot.name}\n' + else: + out += f'**Active Prayer**: none\n' + + if self.active_food: + out += f'**Active Food**: {self.active_food.name}\n\n' + else: + out += f'**Active Food**: none\n\n' + + for slot in armour_print_order: + item = equipment[slot] + out += f'**{string.capwords(slot)}**: ' + if item is not None: + out += f'{item.name} ' + out += f'*(dam: {item.damage}, ' \ + f'acc: {item.accuracy}, ' \ + f'arm: {item.armour}, ' \ + f'pray: {item.prayer})*\n' + else: + out += 'none *(dam: 0, acc: 0, arm: 0, pray: 0)*\n' + + return out + + @property + def unlocked_patches(self): + return FarmPlot.objects.filter(quest_req__in=self.completed_quest_queryset) + @property def usable_prayers(self): prayers = Prayer.objects.filter(level_required__lte=self.prayer_level) - ret = [] - for p in prayers: - if p.quest_req and p.quest_req in self.completed_quests_list: - ret.append(p) - elif not p.quest_req: - ret.append(p) - - return ret + return prayers.filter(Q(quest_req=None) or Q(quest_req__in=self.completed_quest_queryset)) @property def completed_quests_list(self): - return [uq.quest for uq in self.userquest_set.all()] + return list(self.completed_quest_queryset) + + @property + def completed_quest_queryset(self): + return Quest.objects.filter(userquest__user=self) @property def num_quests_complete(self): - return len(self.completed_quests_list) + return len(self.completed_quest_queryset) @property def is_eating(self): @@ -609,3 +782,40 @@ def __repr__(self): def __str__(self): return self.__repr__() + + +def xp_to_level(xp): + """Converts a user's xp into its equivalent level based on an XP table.""" + xp = int(xp) + for level_xp in XP: + if int(level_xp) > xp: + return int(XP[level_xp]) - 1 + else: + return 99 + + +def calc_xp_to_level(author, skill, level): + """Calculates the xp needed to get to a level.""" + author_levels = author.skill_level_mapping + author_xps = author.skill_xp_mapping + + if skill not in author_levels.keys(): + return f'{skill} is not a skill.' + + if level is None: + level = author_levels[skill] + 1 + + if level > 99: + return f'You have already attained the maximum level in this skill.' + + current_xp = author_xps[skill] + for xp_value in XP.keys(): + if XP[xp_value] == level: + xp_needed = int(xp_value) - current_xp + break + else: + raise KeyError + xp_formatted = '{:,}'.format(xp_needed) + + out = f'You need {xp_formatted} xp to get level {level} in {skill}.' + return out diff --git a/miniscape/monster_helpers.py b/miniscape/monster_helpers.py index ecce52f..0a28914 100755 --- a/miniscape/monster_helpers.py +++ b/miniscape/monster_helpers.py @@ -1,3 +1,5 @@ +from django.db.models import Q + from cogs.helper.monsters import MONSTERS, add_plural, get_attr from miniscape.models import Monster, User, MonsterLoot import random @@ -26,20 +28,12 @@ def get_random(author: User, wants_boss=False): """Randomly selects and returns a monster (given a particular boolean key).""" - monster: Monster - if wants_boss: - monster = random.sample(set(Monster.objects.filter(is_boss=True)), 1) - else: - if author.offhand_slot.is_anti_dragon: - monster = random.sample(set(Monster.objects.filter(is_boss=False, - is_slayable=True, - slayer_level_req__lte=author.slayer_level)), 1) - else: - monster = random.sample(set(Monster.objects.filter(is_boss=False, - is_slayable=True, - slayer_level_req__lte=author.slayer_level, - is_dragon=False)), 1) - return monster[0] + monsters = Monster.objects.filter(Q(quest_req=None) | Q(quest_req__in=author.completed_quests_list), + is_boss=wants_boss, + is_slayable=not wants_boss, + slayer_level_req__lte=author.slayer_level, + is_dragon=author.offhand_slot.is_anti_dragon) + return random.sample(monsters, 1)[0] def print_monster_kills(author, search=None): diff --git a/miniscape/quest_helpers.py b/miniscape/quest_helpers.py index af2479a..807e21e 100755 --- a/miniscape/quest_helpers.py +++ b/miniscape/quest_helpers.py @@ -142,7 +142,7 @@ def start_quest(guildid, channelid, user: User, questid): except ObjectDoesNotExist: return f"Error: quest number {questid} does not refer to any quest." - if quest in user.completed_quests_list: + if quest in list(user.completed_quest_queryset): return "Error: you have already done this quest." if not user.has_quest_req_for_quest(quest): diff --git a/miniscape/slayer_helpers.py b/miniscape/slayer_helpers.py index 898ddd2..094cb2e 100755 --- a/miniscape/slayer_helpers.py +++ b/miniscape/slayer_helpers.py @@ -177,18 +177,19 @@ def get_task(guildid, channelid, author: User): equipment = author.equipment_slots for _ in range(1000): - monster = mon.get_random(author, wants_boss=False) + monster: Monster = mon.get_random(author, wants_boss=False) num_to_kill = random.randint(LOWEST_NUM_TO_KILL, LOWEST_NUM_TO_KILL + 15 + 3 * slayer_level) base_time, task_length = calc_length(author, monster, num_to_kill) chance = calc_chance(author, monster, num_to_kill) + quest_req_met = (monster.quest_req in completed_quests) if monster.quest_req else True mon_level = monster.level if 0.25 <= task_length / base_time <= 2 \ and chance >= 20 \ and mon_level / cb_level >= 0.8 \ and task_length <= 3600 \ - and (monster.quest_req and monster.quest_req in completed_quests): + and quest_req_met: break else: log_str = f"Failed to give task to user\n" \ diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/user.py b/models/user.py new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/models/user.py @@ -0,0 +1,3 @@ + + + diff --git a/requirements.txt b/requirements.txt index 1950069..b1e3791 100755 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,6 @@ chardet==3.0.4 decorator==5.1.1 discord.py==1.7.3 Django==2.1.15 - executing==0.8.3 gurobipy==9.1.0 idna==2.6 diff --git a/convert_users.py b/scripts/convert_users.py old mode 100755 new mode 100644 similarity index 100% rename from convert_users.py rename to scripts/convert_users.py diff --git a/scripts/farming/01-create_plots.py b/scripts/farming/01-create_plots.py new file mode 100644 index 0000000..be77a0b --- /dev/null +++ b/scripts/farming/01-create_plots.py @@ -0,0 +1,190 @@ +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "miniscapebot.settings") +import django + +django.setup() + +from miniscape.models import Quest, FarmPlot +from miniscape.models.farming_plot import HERB_PATCH, ALLOTMENT_PATCH, BUSH_PATCH, TREE_PATCH, FRUIT_TREE_PATCH, \ + FLOWER_PATCH + + +def __get_quest(name): + return Quest.objects.get(name__iexact=name) + + +def create_herb_patches(): + lumbridge = FarmPlot(name="Lumbridge herb patch", + nick="lumby", + type=HERB_PATCH.identifier, + quest_req=__get_quest("cook's assistant")) + + crandor = FarmPlot(name="Crandor herb patch", + nick="crandor", + type=HERB_PATCH.identifier, + quest_req=__get_quest("Dragon Slayer, Part 3")) + + east_ardougne = FarmPlot(name="East Ardougne herb patch", + nick="ardy", + type=HERB_PATCH.identifier, + quest_req=__get_quest("Dragon Slayer, Part 3")) + + haunted_mine = FarmPlot(name="Haunted Mine herb patch", + nick="mine", + type=HERB_PATCH.identifier, + quest_req=__get_quest("Haunted Mine")) + lumbridge.save() + crandor.save() + east_ardougne.save() + haunted_mine.save() + return + + +def create_allotment_patches(): + al_kharid = FarmPlot(name="Al-Kharid allotment patch", + nick="al-kh", + type=ALLOTMENT_PATCH.identifier, + quest_req=__get_quest("Dragon Slayer, Part 3")) + + varrock = FarmPlot(name="Varrock allotment patch", + nick="varrock", + type=ALLOTMENT_PATCH.identifier, + quest_req=None) + + deaths_office = FarmPlot(name="Death's office allotment patch", + nick="deaths", + type=ALLOTMENT_PATCH.identifier, + quest_req=__get_quest("Missing, Presumed Death")) + + karamja = FarmPlot(name="Karamja allotment patch", + nick="karamja", + type=ALLOTMENT_PATCH.identifier, + quest_req=__get_quest("nature spirit")) + al_kharid.save() + varrock.save() + deaths_office.save() + karamja.save() + return + + +def create_bush_patches(): + trollheim = FarmPlot(name="Trollheim bush patch", + nick="trollheim", + type=BUSH_PATCH.identifier, + quest_req=__get_quest("Eadgar's Ruse")) + + shilo = FarmPlot(name="Shilo Village office bush patch", + nick="Shilo", + type=BUSH_PATCH.identifier, + quest_req=__get_quest("Shilo Village")) + + lunar_isle = FarmPlot(name="Lunar Isle bush patch", + nick="lunar", + type=BUSH_PATCH.identifier, + quest_req=__get_quest("Lunar Diplomacy")) + + pyramid = FarmPlot(name="Desert Pyramid bush patch", + nick="pyramid", + type=BUSH_PATCH.identifier, + quest_req=__get_quest("Desert Treasure, Part 3")) + trollheim.save() + shilo.save() + lunar_isle.save() + pyramid.save() + return + + +def create_tree_patches(): + falador = FarmPlot(name="Falador Tree patch", + nick="fally", + type=TREE_PATCH.identifier, + quest_req=None) + + tai_bwo_wannai = FarmPlot(name="Tai Bwo Wannai Tree patch", + nick="tai", + type=TREE_PATCH.identifier, + quest_req=__get_quest("nature spirit")) + + watchtower = FarmPlot(name="Watchtower Tree patch", + nick="watchtower", + type=TREE_PATCH.identifier, + quest_req=__get_quest("the watchtower")) + + ape_atoll = FarmPlot(name="Ape Atoll Tree patch", + nick="ape", + type=TREE_PATCH.identifier, + quest_req=__get_quest("monkey madness")) + falador.save() + tai_bwo_wannai.save() + watchtower.save() + ape_atoll.save() + return + + +def create_fruit_tree_patches(): + tzhaar_city = FarmPlot(name="Tzhaar City Fruit Tree patch", + nick="tzhaar", + type=FRUIT_TREE_PATCH.identifier, + quest_req=__get_quest("The Brink of Extinction")) + + meiyerditch = FarmPlot(name="Meiyerditch Fruit Tree patch", + nick="meiyer", + type=FRUIT_TREE_PATCH.identifier, + quest_req=__get_quest("Darkness of Hallovale")) + + froze_prison = FarmPlot(name="Nex's Frozen Prison Fruit Tree patch", + nick="nex", + type=FRUIT_TREE_PATCH.identifier, + quest_req=__get_quest("The Frozen Prison")) + + etc = FarmPlot(name="Etceteria Fruit Tree patch", + nick="etc", + type=FRUIT_TREE_PATCH.identifier, + quest_req=__get_quest("Royal Trouble")) + tzhaar_city.save() + meiyerditch.save() + froze_prison.save() + etc.save() + return + + +def create_flower_patches(): + zanaris = FarmPlot(name="Zanaris flower patch", + nick="zanaris", + type=FLOWER_PATCH.identifier, + quest_req=__get_quest("The Lost City")) + + camelot = FarmPlot(name="Camelot flower patch", + nick="camelot", + type=FLOWER_PATCH.identifier, + quest_req=__get_quest("merlin's crystal")) + + lighthouse = FarmPlot(name="Lighthouse flower patch", + nick="lighthouse", + type=FLOWER_PATCH.identifier, + quest_req=__get_quest("Horror from the Deep")) + + misc = FarmPlot(name="Miscellania Flower patch", + nick="misc", + type=FLOWER_PATCH.identifier, + quest_req=__get_quest("Royal Trouble")) + zanaris.save() + camelot.save() + lighthouse.save() + misc.save() + pass + + +def main(): + create_flower_patches() + create_fruit_tree_patches() + create_tree_patches() + create_allotment_patches() + create_bush_patches() + create_herb_patches() + return + + +if __name__ == "__main__": + main() diff --git a/scripts/farming/02-create_crops.py b/scripts/farming/02-create_crops.py new file mode 100644 index 0000000..7bb013e --- /dev/null +++ b/scripts/farming/02-create_crops.py @@ -0,0 +1,54 @@ +from miniscape.models import Item + +class crop: + def __init__(self, name, nicknames, plural, value, food_value): + self.name = name + self.nicknames = nicknames + self.plural = plural + self.value = value + self.food_value = food_value + + +BUSH_CROPS = { + "redberry": crop("redberries", [], "", 20, 0), + "dwellberry": crop("dwellberries", [], "", 30, 0), + "poison ivy": crop("poison ivy berries", ["poison ivy"], "", 100, 0), + "avocado": crop("avocado", ["shavacado"], "es", 800, 15), + "cactuss fruit": crop("cactuss fruit", ["cactuss"], "s", 1500, 18), + "whiteberries": None, +} + +ALLOTMENT_CROPS = { + "potato": crop("potato", ["tater"], "es", 10, 2), + "onion": crop("onion", ["shrek"], "s", 20, 3), + "cabbage": crop("cabbage", [], "s", 30, 4), + "strawberry": crop("strawberry", ["sberry"], "", 50, 5), + "snape grass": None, +} + +FRUIT_TREE_CROPS = { + "apple": crop("apple", [], "s", 40, 5), + "banana": crop("banana", ["nanner"], "s", 80, 7), + "orange": crop("orange", [], "s", 120, 8), + "papaya": crop("papaya", [], "s", 200, 12), + "coconut": crop("coconut", [], "s", 500, 14), + "mango": crop("mango", [], "es", 3000, 21) +} + +FLOWER_CROPS = { + "marigold": crop("marigold", [], "s", 20, 0), + "peony": crop("peony", [], "s", 80, 0), + "hydrangea": crop("hydrangea", [], "s", 20000, 0), + "limpwurt root": None +} + +""" +Toadflax Herb +Irit Herb +""" +HERB_CROPS = { + +} + +def create_bush_crops(): + pass diff --git a/scripts/farming/03-create_seeds.py b/scripts/farming/03-create_seeds.py new file mode 100644 index 0000000..2091872 --- /dev/null +++ b/scripts/farming/03-create_seeds.py @@ -0,0 +1,112 @@ +from miniscape.models import Item, ItemNickname +from miniscape.models.farming_plot import TREE_PATCH, BUSH_PATCH, ALLOTMENT_PATCH, FRUIT_TREE_PATCH, FLOWER_PATCH, \ + HERB_PATCH + + +class seed: + def __init__(self, name, level, patch_type, nicknames, plural, value, xp): + self.name = name + self.level = level + self.patch_type = patch_type + self.nicknames = nicknames + self.plural = plural + self.value = value + self.xp = xp + + def create_and_persist_object(self): + try: + i = Item.objects.get(name=self.name) + except Item.DoesNotExist: + i = Item( + name=self.name, + plural=self.plural, + value=self.value, + is_farmable=True, + ) + i.save() + + for nick in self.nicknames: + try: + ItemNickname.objects.get(nickname=nick) + except ItemNickname.DoesNotExist: + nn = ItemNickname(real_item=i, nickname=nick) + nn.save() + + +TREE_SEEDS = { + "acorn": seed("acorn", 15, TREE_PATCH.identifier, [], "s", 1000, 500), + "willow": seed("willow tree seed", 30, TREE_PATCH.identifier, ["willow seed"], "s", 3000, 1500), + "maple": seed("maple tree seed", 45, TREE_PATCH.identifier, ["maple seed"], "s", 7500, 3500), + "yew": seed("yew tree seed", 60, TREE_PATCH.identifier, ["yew seed"], "s", 20000, 7000), + "magic": seed("magic tree seed", 75, TREE_PATCH.identifier, ["magic seed"], "s", 50000, 14000), + "elder": seed("elder tree seed", 90, TREE_PATCH.identifier, ["elder seed"], "s", 100000, 25000) +} + +BUSH_SEEDS = { + "redberry": seed("redberry seed", 8, BUSH_PATCH.identifier, [], "s", 100, 50), + "dwellberry": seed("dwellberry seed", 20, BUSH_PATCH.identifier, [], "s", 200, 200), + "whiteberry": seed("whiteberry seed", 60, BUSH_PATCH.identifier, [], "s", 5000, 450), + "poison ivy": seed("poison ivy seed", 70, BUSH_PATCH.identifier, [], "s", 10000, 800), + "avocado": seed("avocado seed", 80, BUSH_PATCH.identifier, [], "s", 15000, 4000), + "cactuss": seed("cactuss seed", 95, BUSH_PATCH.identifier, ["guncle seed", "cactussy"], "s", 25000, 15000) +} + +ALLOTMENT_SEEDS = { + "potato": seed("potato seed", 1, ALLOTMENT_PATCH.identifier, [], "s", 3, 9), + "onion": seed("onion seed", 5, ALLOTMENT_PATCH.identifier, [], "s", 7, 11), + "cabbage": seed("cabbage seed", 10, ALLOTMENT_PATCH.identifier, [], "s", 11, 15), + "strawberry": seed("strawberry seed", 20, ALLOTMENT_PATCH.identifier, [], "s", 30, 29), + "snape grass": seed("snape grass seed", 50, ALLOTMENT_PATCH.identifier, [], "s", 70, 80), +} + +FRUIT_TREE_SEEDS = { + "apple": seed("apple tree seed", 15, FRUIT_TREE_PATCH.identifier, [], "s", 1000, 900), + "banana": seed("banana tree seed", 27, FRUIT_TREE_PATCH.identifier, [], "s", 1776, 2200), + "orange": seed("orange tree seed", 39, FRUIT_TREE_PATCH.identifier, [], "s", 2048, 3000), + "papaya": seed("papaya tree seed", 58, FRUIT_TREE_PATCH.identifier, [], "s", 6200, 7000), + "palm": seed("palm tree seed", 69, FRUIT_TREE_PATCH.identifier, [], "s", 9999, 12000), + "mango": seed("mango tree seed", 95, FRUIT_TREE_PATCH.identifier, [], "s", 250000, 30000) +} + +FLOWER_SEEDS = { + "marigold": seed("marigold seed", 1, FLOWER_PATCH.identifier, [], "s", 3, 25), + "peony": seed("peony seed", 5, FLOWER_PATCH.identifier, [], "s", 10, 50), + "limpwurt": seed("limpwurt root seed", 15, FLOWER_PATCH.identifier, ["limp seed"], "s", 20, 90), + "hydrangea": seed("hydrangea seed", 50, FLOWER_PATCH.identifier, ["hydr seed"], "s", 50, 400) +} + +HERB_SEEDS = { + "guam seed": seed("guam seed", 1, HERB_PATCH.identifier, [], "s", 3, 13), + "irit seed": seed("irit seed", 40, HERB_PATCH.identifier, [], "s", 120, 45), + "toadflax seed": seed("toadflax seed", 35, HERB_PATCH.identifier, [], "s", 110, 40) +} + + +def create_tree_seeds(): + for s in TREE_SEEDS.values(): + s.create_and_persist_object() + + +def create_bush_seeds(): + for s in BUSH_SEEDS.values(): + s.create_and_persist_object() + + +def create_allotment_seeds(): + for s in ALLOTMENT_SEEDS.values(): + s.create_and_persist_object() + + +def create_fruit_tree_seeds(): + for s in FRUIT_TREE_SEEDS.values(): + s.create_and_persist_object() + + +def create_flower_seeds(): + for s in FLOWER_SEEDS.values(): + s.create_and_persist_object() + + +def create_herb_seeds(): + for s in HERB_SEEDS.values(): + s.create_and_persist_object() diff --git a/scripts/farming/04-assign_seed_loot.py b/scripts/farming/04-assign_seed_loot.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/farming/05-create-farm-recipes.py b/scripts/farming/05-create-farm-recipes.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/ipython-script.py b/scripts/ipython-script.py new file mode 100644 index 0000000..880babc --- /dev/null +++ b/scripts/ipython-script.py @@ -0,0 +1,12 @@ +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "miniscapebot.settings") +import django +django.setup() +from miniscape.models import * +from django.db.models import Q + +me = User.objects.get(pk=147501762566291457) +ca = Quest.objects.get(name__iexact="cook's assistant") +bronze_sword = Item.objects.get(name__iexact="bronze sword") +chicken = Monster.objects.get(name__iexact="chicken") +bronze_sword_recipe = Recipe.objects.filter(creates=bronze_sword)