Skip to content

Commit

Permalink
update nether deer, add unposter, allow juicing mc mushrooms, book setup
Browse files Browse the repository at this point in the history
  • Loading branch information
eerussianguy committed Oct 2, 2024
1 parent d017d84 commit 523e833
Show file tree
Hide file tree
Showing 85 changed files with 2,077 additions and 101 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ val jeiVersion: String = "15.2.0.21"
val patchouliVersion: String = "1.20.1-81-FORGE"
val jadeVersion: String = "4614153"
val topVersion: String = "4629624"
val tfcVersion: String = "5478226"
val tfcVersion: String = "5571484"

val modId: String = "beneath"

Expand Down
10 changes: 8 additions & 2 deletions resources/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ def generate(rm: ResourceManager):
}).with_lang(lang('burpflower')).with_tag('tfc:plants').with_block_loot('beneath:burpflower')
rm.item_model('burpflower', 'beneath:block/plant/burpflower_base')

for i in range(1, 9):
rm.block_model('unposter_%s' % i, parent='beneath:block/compost_%s' % i, textures={'0': 'beneath:block/unposter/normal'})
rm.block_model('unposter_0', parent='beneath:block/unposter', no_textures=True)
rm.blockstate('unposter', variants=dict(('stage=%s' % i, {'model': 'beneath:block/unposter_%s' % i}) for i in range(0, 9))).with_lang(lang('unposter')).with_block_loot('beneath:unposter')
rm.item_model('unposter', parent='beneath:block/unposter', no_textures=True)

rm.blockstate('cursecoal_pile', variants=dict((('layers=%d' % i), {'model': 'beneath:block/pile/cursecoal_height%d' % (i * 2) if i != 8 else 'beneath:block/pile/cursecoal_block'}) for i in range(1, 1 + 8))).with_lang(lang('Cursecoal Pile')).with_block_loot('beneath:cursecoal')
rm.block_model('pile/cursecoal_block', textures={'all': 'beneath:block/cursecoal'})
rm.blockstate('hellforge', variants=dict((('heat_level=%d' % i), {'model': 'beneath:block/hellforge/heat_%d' % i}) for i in range(0, 7 + 1))).with_lang(lang('Hellforge')).with_block_loot('7 beneath:cursecoal')
Expand Down Expand Up @@ -131,9 +137,9 @@ def generate(rm: ResourceManager):
rm.blockstate('crackrack').with_block_loot(loot_tables.alternatives({'name': 'beneath:crackrack', 'conditions': [{'condition': 'tfc:is_isolated'}]}, '2-4 beneath:crackrack_rock')).with_lang(lang('crackrack')).with_block_model().with_tag('minecraft:mineable/pickaxe').with_tag('minecraft:base_stone_nether').with_item_model()

for shroom in MUSHROOMS:
mushlang = '%s mushroom' % shroom.replace('fools', 'Fool\'s')
mushlang = lang(shroom) if 'fools' not in shroom else 'Fool\'s Funnel'
rm.blockstate('mushroom/%s' % shroom).with_block_model(parent='block/cross', textures={'cross': 'beneath:block/mushroom/%s' % shroom}).with_block_loot('beneath:food/%s' % shroom).with_tag('tfc:plants').with_tag('tfc:mineable_with_sharp_tool').with_lang(lang(mushlang))
rm.item_model('food/%s' % shroom, 'beneath:block/mushroom/%s' % shroom).with_lang(lang(mushlang))
rm.item_model('food/%s' % shroom, 'beneath:block/mushroom/%s' % shroom).with_lang(mushlang)

simple_block(rm, 'cobblerack', 'minecraft:mineable/pickaxe', 'forge:cobblestone')
simple_block(rm, 'fungal_cobblerack', 'minecraft:mineable/pickaxe')
Expand Down
2 changes: 2 additions & 0 deletions resources/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Rock(NamedTuple):
category: str
sand: str

ROCK_CATEGORIES: List[str] = ['sedimentary', 'metamorphic', 'igneous_extrusive', 'igneous_intrusive']

TFC_METALS: Dict[str, Metal] = {
'gold': Metal(1, {'part'}, 0.6, 1060, None),
Expand Down Expand Up @@ -118,6 +119,7 @@ class Rock(NamedTuple):
'beneath.nutrient.sorrow': 'Sorrow: §9%s%%',
'beneath.block_entity.hellforge': 'Hellforge',
'beneath.screen.juicer': 'Juicer',
'beneath.screen.juicer.mushrooms': 'Feed me mushrooms!',
'item.beneath.juicer.filled': 'Juicer (%s)',
'death.attack.beneath.sulfur': '%1$s mined sulfur with an iron tool and blew themselves up.',
'death.attack.beneath.sulfur.player': '%1$s mined sulfur with an iron tool and blew themselves up while trying to escape %2$s.',
Expand Down
3 changes: 2 additions & 1 deletion resources/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def generate(rm: ResourceManager):

### ITEM TAGS ###
rm.item_tag('sparks_on_sulfur', *['#tfc:metal_item/%s' % metal for metal in ('black_steel', 'blue_steel', 'red_steel', 'steel', 'wrought_iron', 'cast_iron')])
rm.item_tag('usable_in_juicer', '#tfc:foods/fruits', '#beneath:mushrooms')
rm.item_tag('usable_in_juicer', '#tfc:foods/fruits', '#beneath:mushrooms', 'minecraft:warped_fungus', 'minecraft:crimson_fungus')
rm.item_tag('unpostable', '#beneath:mushrooms', 'minecraft:warped_fungus', 'minecraft:crimson_fungus', 'beneath:ghost_pepper', 'beneath:gleamflower', 'minecraft:crimson_roots', 'minecraft:warped_roots', 'minecraft:nether_wart', 'minecraft:ghast_tear')

block_and_item_tag(rm, 'tfc:rock/aqueduct', 'beneath:blackstone_aqueduct')
rm.item_tag('tfc:rock_knapping', 'beneath:nether_pebble', 'beneath:blackstone_pebble')
Expand Down
89 changes: 89 additions & 0 deletions resources/format_lang.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import difflib
import json

from typing import Tuple


def main(validate: bool, namespace: str, langs: Tuple[str, ...]):
en_us = load(namespace, 'en_us')
for lang in langs:
if lang != 'en_us':
format_lang(namespace, en_us, lang, validate)


def update(namespace: str, langs: Tuple[str, ...]):
en_us = load(namespace, 'en_us')
en_us_old = load_old(namespace, 'en_us')
updated_keys = {k for k in en_us.keys() if k in en_us_old and en_us[k] != en_us_old[k]}

if updated_keys:
print('Found %d modified values:' % len(updated_keys))
for k in updated_keys:
print('Modified: %s : "%s" -> "%s"' % (k, en_us_old[k], en_us[k]))

inp = input('Remove these keys from other translations?\n(yes|no) >')
print('Answer: %s' % inp)
if inp == 'yes':
# Strip these keys from en_us, so they don't show up in translations
for k in updated_keys:
del en_us[k]
for lang in langs:
if lang != 'en_us':
format_lang(namespace, en_us, lang, False)
else:
print('No differences found')


def format_lang(namespace: str, en_us, lang: str, validate: bool):
lang_data = load(namespace, lang)
lang_comments = {k: v for k, v in lang_data.items() if '__comment' in k and v != 'This file was automatically created by mcresources'}
lang_data = {k: v for k, v in lang_data.items() if '__comment' not in k}

formatted_lang_data = {}
for k, v in lang_comments.items():
formatted_lang_data[k] = v

translated = 0
for k, v in en_us.items():
if '__comment' in k:
pass # Exclude comments in en_us
elif k in lang_data and lang_data[k] != v:
translated += 1
formatted_lang_data[k] = lang_data[k]
else:
formatted_lang_data[k] = v

# Unique keys to this language, only allowed in the default vanilla overrides. It makes no sense for a language to have uniquely named TFC keys
# But, for vanilla minecraft, we may have to override for vanilla items we rename without renaming.
# e.g. we use 'Egg' but if a translation is 'Chicken Egg', that might be renamed for other languages only.
if namespace == 'minecraft':
for k, v in lang_data.items():
if k not in en_us:
formatted_lang_data[k] = v

print('Translation progress for %s (%s): %d / %d (%.1f%%)' % (lang, namespace, translated, len(en_us), 100 * translated / len(en_us)))
save(namespace, lang, formatted_lang_data, validate)


def load(namespace: str, lang: str):
with open('./src/main/resources/assets/%s/lang/%s.json' % (namespace, lang), 'r', encoding='utf-8') as f:
return json.load(f)


def load_old(namespace: str, lang: str):
""" The old lang file need to be manually placed under the project root and
be named as exactly `<lang>.<namespace>.old.json`, where <lang> is the
language code, and <namespace> is usually either 'minecraft' or 'tfc'.
"""
with open('./%s.%s.old.json' % (lang, namespace), 'r', encoding='utf-8') as f:
return json.load(f)


def save(namespace: str, lang: str, lang_data, validate: bool):
if validate:
with open('./src/main/resources/assets/%s/lang/%s.json' % (namespace, lang), 'r', encoding='utf-8') as f:
old_lang_data = json.load(f)
assert old_lang_data == lang_data, 'Validation error in mod localization for %s:\n\n=== Diff (expected vs. actual) ===\n\n%s' % (lang, '\n'.join(difflib.unified_diff(json.dumps(lang_data, ensure_ascii=False, indent=2).split('\n'), json.dumps(old_lang_data, ensure_ascii=False, indent=2).split('\n'))))
else:
with open('./src/main/resources/assets/%s/lang/%s.json' % (namespace, lang), 'w', encoding='utf-8') as f:
json.dump(lang_data, f, ensure_ascii=False, indent=2)
107 changes: 107 additions & 0 deletions resources/generate_book.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import format_lang
from patchouli import *
from argparse import ArgumentParser
from typing import Optional

BOOK_LANGUAGES = ('en_us',)
MOD_LANGUAGES = ('en_us',)

class LocalInstance:
INSTANCE_DIR = None

@staticmethod
def wrap(rm: ResourceManager):
def data(name_parts: ResourceIdentifier, data_in: JsonObject, root_domain: str = 'data'):
return rm.write((LocalInstance.INSTANCE_DIR, '/'.join(utils.str_path(name_parts))), data_in)

if LocalInstance.INSTANCE_DIR is not None:
rm.data = data
return rm
return None

def main_with_args():
parser = ArgumentParser('generate_book.py')
parser.add_argument('--translate', type=str, default='en_us', help='The language to translate to')
parser.add_argument('--local', type=str, default=None, help='The directory of a local .minecraft to copy into')
parser.add_argument('--translate-all', type=str, default=None, help='If all languages should be translated')
parser.add_argument('--format', type=str, default=None, help='Format the mod languages')
parser.add_argument('--reverse-translate', type=str, default=None, help='Reverse a translation from the mod files.')

args = parser.parse_args()

if args.format:
do_format()
return

if args.translate_all:
do_format()
for la in BOOK_LANGUAGES:
main(la, args.local, False, reverse_translate=args.reverse_translate is not None)
else:
main(args.translate, args.local, False, reverse_translate=args.reverse_translate is not None)

def do_format():
# format_lang.main(False, 'minecraft', BOOK_LANGUAGES)
format_lang.main(False, 'beneath', MOD_LANGUAGES)

def main(translate_lang: str, local_minecraft_dir: Optional[str], validate: bool, validating_rm: ResourceManager = None, reverse_translate: bool = False):
LocalInstance.INSTANCE_DIR = local_minecraft_dir

rm = ResourceManager('tfc', './src/main/resources')
if validate:
rm = validating_rm
i18n = I18n(translate_lang, validate)

print('Writing book at %s' % translate_lang)
make_book(rm, i18n, local_instance=False, reverse_translate=reverse_translate)

i18n.flush()

if LocalInstance.wrap(rm):
print('Copying %s book into local instance at: %s' % (translate_lang, LocalInstance.INSTANCE_DIR))
make_book(rm, I18n(translate_lang, validate), local_instance=True)


# def main():
# for language in BOOK_LANGUAGES:
# rm = ResourceManager('tfc', '../src/main/resources')
# i18n = I18n.create(language)
#
# print('Writing book %s' % language)
# make_book(rm, i18n)
#
# i18n.flush()
#
# if LocalInstance.wrap(rm) and language == 'en_us':
# print('Copying into local instance at: %s' % LocalInstance.INSTANCE_DIR)
# make_book(rm, I18n.create('en_us'), local_instance=True)
#
# print('Done')

def make_book(rm: ResourceManager, i18n: I18n, local_instance: bool = False, reverse_translate: bool = False):
book = Book(rm, 'field_guide', {}, i18n, local_instance, reverse_translate)

book.category('beneath', 'Beneath', 'All about what is Beneath', 'beneath:cursecoal', is_sorted=True, entries=(
entry('beneath', 'What Lies Beneath', 'beneath:textures/item/cursecoal.png', pages=(
text('XXX'),
empty_last_page()
)),
))

book.build()

# beneath Pages

def knapping(recipe: str, text_content: TranslatableStr) -> Page: return recipe_page('knapping_recipe', recipe, text_content)

def alloy_recipe(title: str, ingot: str, *components: Tuple[str, int, int], text_content: str) -> Page:
recipe = ''.join(['$(li)%d - %d %% : $(thing)%s$()' % (lo, hi, alloy) for (alloy, lo, hi) in components])
return item_spotlight(ingot, title, False, '$(br)$(bold)Requirements:$()$(br)' + recipe + '$(br2)' + text_content)

def custom_component(x: int, y: int, class_name: str, data: JsonObject) -> Component:
return Component('patchouli:custom', x, y, {'class': 'com.eerussianguy.beneath.compat.patchouli.' + class_name, **data})


if __name__ == '__main__':
main_with_args()

85 changes: 85 additions & 0 deletions resources/i18n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import json
import os

import Levenshtein


class I18n:

lang: str

def __init__(self, lang: str, validate: bool = False):
self.lang = lang
self.before = {}
self.after = {}
self.validate = validate
self.lang_path = './resources/lang/%s.json' % lang

self.fuzzy_matches = 0
self.fuzzy_non_matches = 0

# Default translation
if not os.path.isfile(self.lang_path):
if validate:
raise ValueError('Cannot validate book for lang %s, as resources/lang/%s.json does not exist' % (lang, lang))
print('Writing default translation for language %s to %s' % (self.lang, self.lang_path))
with open(self.lang_path, 'w', encoding='utf-8') as f:
f.write('{}\n')

# Read the existing translation
with open(self.lang_path, 'r', encoding='utf-8') as f:
print('Reading translation for language %s to %s' % (self.lang, self.lang_path))
j = json.load(f)

# Parse json
for key, value in j.items():
if not isinstance(value, str):
print('Illegal translation entry: "%s": "%s"' % (key, value))
exit(-1)
self.before[key] = value

def is_root(self) -> bool:
""" Return true if we are in the root language (en_us) """
return self.lang == 'en_us'

def translate(self, text: str) -> str:
""" Translates the string into the current domain """
if self.is_root():
# For en_us, always keep the current text (read only)
translated = text
elif text in self.before:
translated = self.before[text] # Translate if available
else:
# Try a fuzzy matcher (if we're not in en_us)
# Use the lowercase of both keys, as difference in capitalization is almost surely not a translation issue
distance, match = min(((Levenshtein.distance(text.lower(), key.lower()), key) for key in self.before.keys()))
if distance / len(text) < 0.1 and distance < 20: # Heuristic: < 5% of text, and < 20 overall distance
if self.before[match] == match:
# This has just matched a default key that was inserted in the translated files
# So if we slightly modify the en_us default, we should change this value as well.
self.fuzzy_non_matches += 1
translated = text
else:
# Use the fuzzy match
self.fuzzy_matches += 1
translated = self.before[match]
else:
# Not available, but record and output anyway
self.fuzzy_non_matches += 1
translated = text

self.after[text] = translated
return translated

def flush(self):
""" Updates the local translation file, if needed """
if not self.is_root() and self.fuzzy_matches + self.fuzzy_non_matches > 0:
print('Matched %d / %d entries (%.1f%%). Updated %d entries for lang %s.' % (self.fuzzy_matches, self.fuzzy_matches + self.fuzzy_non_matches, 100 * self.fuzzy_matches / (self.fuzzy_matches + self.fuzzy_non_matches), self.fuzzy_non_matches, self.lang))
if self.validate:
assert self.before == self.after, 'Validation error translating book to lang \'%s\'' % self.lang
with open(self.lang_path, 'w', encoding='utf-8') as f:
unique_count = len(self.after) if self.is_root() else sum(k != v for k, v in self.after.items())
if unique_count > 0:
print('Writing updated translation for language %s: %d / %d (%.2f%%)' % (self.lang, unique_count, len(self.after), 100 * unique_count / len(self.after)))
json.dump(self.after, f, indent=2, ensure_ascii=False)

1 change: 1 addition & 0 deletions resources/models/Nether deer.bbmodel

Large diffs are not rendered by default.

Loading

0 comments on commit 523e833

Please sign in to comment.