summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--mc-resources.py308
-rw-r--r--run.sh8
3 files changed, 318 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c15e68e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+env/*
+from-jar/*
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)}')
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..c945ca5
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,8 @@
+if [ ! -e env ]; then
+ echo creating venv...
+ python3 -m venv env
+ env/bin/pip3 install fuzzywuzzy[speedup]
+ echo venv created
+fi
+
+env/bin/python3 ./mc-resources.py