diff options
Diffstat (limited to 'mc-resources.py')
-rw-r--r-- | mc-resources.py | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/mc-resources.py b/mc-resources.py new file mode 100644 index 0000000..2614980 --- /dev/null +++ b/mc-resources.py @@ -0,0 +1,308 @@ +import fuzzywuzzy.process +import itertools +import graphlib +import zipfile +import json +import os + +def ask_from_list(prompt, mapping): + keys = list(mapping.keys()) + keys.sort() + max_len = len(str(len(keys))) + print(prompt) + for i, x in enumerate(keys): + print(" " * (max_len - len(str(i + 1))) + f' ({i + 1}) {x}') + response = input('> ') + try: + i = int(response) + if i >= 1 and i <= len(mapping): + return mapping[keys[i - 1]] + except ValueError: + pass + return mapping[fuzzywuzzy.process.extractOne(response, keys)[0]] + +def join_with_and(list): + if len(list) == 1: + return list[0] + if len(list) == 2: + return list[0] + ' and ' + list[1] + return ', '.join(list[:-1]) + ', and ' + list[-1] + +if not os.path.exists('from-jar'): + print('please enter the path to the .minecraft folder:') + versions_dir = os.path.join(input('> '), 'versions') + + versions = {} + + for dir in os.walk(versions_dir): + for file in dir[2]: + if file.endswith('.jar'): + versions[file.removesuffix('.jar')] = os.path.join(dir[0], file) + + jar_path = ask_from_list('which version should we extract?', versions) + + with zipfile.ZipFile(jar_path, 'r') as jar: + + print('extracting files from jar...') + interesting_dirs = [ + os.path.join('data', 'minecraft', 'recipes'), + os.path.join('data', 'minecraft', 'tags', 'items'), + os.path.join('assets', 'minecraft', 'lang') + ] + + for file in jar.namelist(): + for dir in interesting_dirs: + if file.startswith(dir): + jar.extract(file, 'from-jar') + +print('loading data...') + +id_to_name_table = {} +name_to_id_table = {} +tag_lists = {} +# recipe is (method, [(input, count)], output, count) +recipe_lists = {} + +with open(os.path.join('from-jar', 'assets', 'minecraft', 'lang', 'en_us.json')) as file: + data = json.load(file) + for key in data: + id = None + if key.startswith('block.minecraft.'): + id = 'minecraft:' + key.removeprefix('block.minecraft.') + if '.' in id: + continue + elif key.startswith('item.minecraft.'): + id = 'minecraft:' + key.removeprefix('item.minecraft.') + if '.' in id: + continue + else: + continue + name = data[key].lower() + name_to_id_table[name] = id + id_to_name_table[id] = name + +def to_display_name(id): + return id_to_name_table[id] if id in id_to_name_table else '<' + id + '>' + +tags_dir = os.path.join('from-jar', 'data', 'minecraft', 'tags', 'items') +for fname in os.listdir(tags_dir): + with open(os.path.join(tags_dir, fname)) as file: + data = json.load(file) + tag_lists['minecraft:' + fname.removesuffix('.json')] = data['values'] + +def add_recipe(recipe): + output = recipe[2] + if output in recipe_lists: + if recipe not in recipe_lists[output]: + recipe_lists[output].append(recipe) + else: + recipe_lists[output] = [recipe] + +def ilist_product(ilist): + ilist_new = [] + for il in ilist: + il_new = [] + if not isinstance(il, list): + il = [il] + for i in il: + if 'tag' in i: + il_new += tag_lists[i['tag']] + else: + il_new.append(i['item']) + ilist_new.append(il_new) + return itertools.product(*ilist_new) + +def add_shaped_recipe(recipe): + output = recipe['result']['item'] + count = recipe['result']['count'] if 'count' in recipe['result'] else 1 + + the_keys = list(recipe['key'].keys()) + + for choice in ilist_product([recipe['key'][key] for key in the_keys]): + map = {} + for i in range(len(the_keys)): + map[the_keys[i]] = choice[i] + counts = {} + for p in ''.join(recipe['pattern']): + if p != ' ': + item = map[p] + if item in counts: + counts[item] += 1 + else: + counts[item] = 1 + items = list(counts.keys()) + items.sort() + add_recipe(('craft', [(item, counts[item]) for item in items], output, count)) + +def add_shapeless_recipe(recipe): + output = recipe['result']['item'] + count = recipe['result']['count'] if 'count' in recipe['result'] else 1 + + for choice in ilist_product(recipe['ingredients']): + counts = {} + for item in choice: + if item in counts: + counts[item] += 1 + else: + counts[item] = 1 + items = list(counts.keys()) + items.sort() + add_recipe(('craft', [(item, counts[item]) for item in items], output, count)) + +def add_stonecut_recipe(recipe): + output = recipe['result'] + count = recipe['count'] + for choice in ilist_product([recipe['ingredient']]): + add_recipe(('stonecut', [(choice[0], 1)], output, count)) + +def add_smelt_recipe(recipe): + output = recipe['result'] + count = 1 + for choice in ilist_product([recipe['ingredient']]): + add_recipe(('smelt', [(choice[0], 1)], output, count)) + +smelting_methods = ['minecraft:smelting', 'minecraft:campfire_cooking', 'minecraft:smoking', 'minecraft:blasting'] + +recipes_dir = os.path.join('from-jar', 'data', 'minecraft', 'recipes') +for fname in os.listdir(recipes_dir): + with open(os.path.join(recipes_dir, fname)) as file: + data = json.load(file) + if data['type'] == 'minecraft:crafting_shaped': + add_shaped_recipe(data) + elif data['type'] == 'minecraft:crafting_shapeless': + add_shapeless_recipe(data) + elif data['type'] == 'minecraft:stonecutting': + add_stonecut_recipe(data) + elif data['type'] in smelting_methods: + add_smelt_recipe(data) + elif data['type'].startswith('minecraft:crafting_special_') or data['type'] in ['minecraft:smithing_trim', 'minecraft:crafting_decorated_pot', 'minecraft:smithing_transform']: + pass + else: + print('unrecognized type ' + data['type']) + exit(1) + +past_recipe_choices_for_items = {} +def choose_recipe_for_item(item): + if item in past_recipe_choices_for_items: + return past_recipe_choices_for_items[item] + if item not in recipe_lists: + return None + recipes = recipe_lists[item] + choices = {'gather': None} + for recipe in recipes: + choices[f'{recipe[0]} from {join_with_and([f"{i[1]} {to_display_name(i[0])}" for i in recipe[1]])}'] = recipe + recipe = ask_from_list('how would you like to obtain ' + to_display_name(item) + '?', choices) + past_recipe_choices_for_items[item] = recipe + return recipe + +# dependees +item_deps_added = [] + +# dependency -> dependee +item_deps = {} + +items_needed = {} + +def add_item_deps(item): + if item not in item_deps_added: + item_deps_added.append(item) + if item not in item_deps: + item_deps[item] = [] + recipe = choose_recipe_for_item(item) + if recipe is not None: + for dep, _ in recipe[1]: + if dep in item_deps: + item_deps[dep].append(item) + else: + item_deps[dep] = [item] + add_item_deps(dep) + +def add_needed_item(item, count): + if item in items_needed: + items_needed[item] += count + else: + items_needed[item] = count + +while True: + print('what item would you like to obtain?') + item = fuzzywuzzy.process.extractOne(input('> '), id_to_name_table)[2] + while True: + print('how many ' + to_display_name(item) + '?') + s = input('> ') + try: + i = int(s) + if i > 0: + add_needed_item(item, i) + except ValueError: + continue + break + if ask_from_list('add more items?', {'yes': True, 'no': False}): + continue + break + +while True: + for item in items_needed: + add_item_deps(item) + + original_needed = items_needed.copy() + steps = [] + gather_steps = [] + + ts = graphlib.TopologicalSorter(item_deps) + + try: + for item in ts.static_order(): + if item in items_needed: + needed = items_needed[item] + if needed == 0: + continue + recipe = choose_recipe_for_item(item) + if recipe is None: + gather_steps.append(f'gather {needed} {to_display_name(item)}') + add_needed_item(item, -needed) + else: + count = (needed - 1) // recipe[3] + 1 + steps.append(f'{recipe[0]} {recipe[3] * count} {to_display_name(recipe[2])} from {join_with_and([f"{c * count} {to_display_name(i)}" for i, c in recipe[1]])}') + for i, c in recipe[1]: + add_needed_item(i, c * count) + add_needed_item(item, -recipe[3] * count) + + except graphlib.CycleError as ex: + print('cycle detected: ' + ' from '.join(to_display_name(id) for id in ex.args[1])) + mapping = {} + for id in ex.args[1][1:]: + mapping[to_display_name(id)] = id + id = ask_from_list('change recipe for which item?', mapping) + past_recipe_choices_for_items.pop(id) + choose_recipe_for_item(id) + items_needed = original_needed + item_deps_added = [] + item_deps = {} + continue + + break + +steps.reverse() +print('steps:') +for step in gather_steps: + print(' ' + step) +for step in steps: + print(' ' + step) + +requested = list(original_needed.keys()) +requested.sort() +print('you should now have the requested items:') +for item in requested: + print(f' {original_needed[item]} {to_display_name(item)}') + +extra = {} +for item in items_needed: + if items_needed[item] != 0: + extra[item] = -items_needed[item] + +if len(extra) != 0: + items = list(extra.keys()) + items.sort() + print('plus these extras:') + for i in items: + print(f' {extra[i]} {to_display_name(i)}') |