Datafix worlds: world type variants for the level.dat generator settings

This commit is contained in:
Ludovic Marie
2026-10-13 00:46:45 +02:00
parent 26462a168b
commit 951b43daac
3 changed files with 41 additions and 22 deletions
+2 -2
View File
@@ -53,7 +53,7 @@ ENTITIES = [
(1631, 2514, 'evoker_fangs', 'OwnerUUIDMost:35L,OwnerUUIDLeast:36L'),
(1631, 2514, 'shulker', 'Rotation:[40.0f,0.0f],Peek:0b'),
(1631, 2514, 'bat', ''),
(2100, 2514, "bee", 'HurtBy:"%s",HasNectar:1b,Anger:50' % U1),
(2100, 2514, 'bee', 'HurtBy:"%s",HasNectar:1b,Anger:50' % U1),
(2100, 2514, 'villager', 'VillagerData:{profession:"minecraft:farmer",level:2,type:"minecraft:plains"},Xp:20,'
'Brain:{memories:{"minecraft:job_site":{pos:[I;1,2,3],dimension:"minecraft:overworld"}}}'),
]
@@ -73,7 +73,7 @@ BLOCK_ENTITIES = [
'final_state:"minecraft:air"}'),
(1900, 2514, 'jigsaw[facing=east]', '{attachement_type:"minecraft:empty",target_pool:"minecraft:empty"}'),
(1900, 2514, 'lectern', '{Book:{id:"minecraft:written_book",Count:1b,tag:{title:"L",author:"A",pages:[\'"x"\']}},Page:0}'),
(2100, 2514, "bee"ive', '{Bees:[{EntityData:{id:"minecraft:bee"},TicksInHive:3,MinOccupationTicks:600}]}'),
(2100, 2514, 'beehive', '{Bees:[{EntityData:{id:"minecraft:bee"},TicksInHive:3,MinOccupationTicks:600}]}'),
]
BLOCK_ENTITIES = [b for b in BLOCK_ENTITIES if b]
+38 -19
View File
@@ -39,6 +39,21 @@ LOCAL = ROOT / '.local' / 'datafix'
SHARED = (ROOT / '.local' / 'minecraft').resolve().parent / 'datafix'
VERSIONS = ['1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.1', '1.16.5', '1.17.1', '1.18.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2', '1.20.4', '1.20.5', '1.20.6', '1.21.1',
'1.21.3', '1.21.4', '1.21.5', '1.21.6', '1.21.8', '1.21.9', '1.21.10', '1.21.11', '26.1', '26.1.2', '26.2']
# Extra worlds of one version with another world type, for the level.dat generator settings:
# name -> (version, server.properties). They get no scene, only a joined player.
VARIANTS = {
'1.13.2-flat': ('1.13.2', {'level-type': 'flat', 'generator-settings':
'3;minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village,oceanmonument,mansion(distance=30)'}),
'1.14.4-flat': ('1.14.4', {'level-type': 'flat', 'generator-settings':
'3;minecraft:bedrock,3*minecraft:stone,minecraft:grass_block;2;stronghold(distance=20 count=5),village(distance=12),'
'oceanmonument(spacing=10 separation=4)'}),
'1.14.4-largebiomes': ('1.14.4', {'level-type': 'largebiomes'}),
'1.14.4-amplified': ('1.14.4', {'level-type': 'amplified'}),
'1.13.2-debug': ('1.13.2', {'level-type': 'debug_all_block_states'}),
'1.14.4-buffet': ('1.14.4', {'level-type': 'buffet', 'generator-settings':
'{"chunk_generator":{"type":"minecraft:surface"},"biome_source":{"type":"minecraft:fixed","options":'
'{"biomes":["minecraft:desert"]}}}'}),
}
MANIFEST = 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json'
LOCK = json.loads((ROOT / 'research-lock.json').read_text())['java_runtimes']
BOT = 'Upgrader'
@@ -136,7 +151,7 @@ def install_runtime(pin, home):
class Server:
def __init__(self, directory, jar, port, packs=(), java='java'):
def __init__(self, directory, jar, port, packs=(), java='java', props_override=None):
directory.mkdir(parents=True, exist_ok=True)
(directory / 'eula.txt').write_text('eula=true\n')
props = {'server-port': port, 'online-mode': 'false', 'level-name': 'world', 'level-seed': '4242',
@@ -144,6 +159,7 @@ class Server:
'enable-rcon': 'false', 'enable-query': 'false', 'max-tick-time': '-1'}
if packs:
props['initial-enabled-packs'] = ','.join(['vanilla', *packs])
props.update(props_override or {})
(directory / 'server.properties').write_text(''.join(f'{k}={v}\n' for k, v in props.items()))
self.process = subprocess.Popen([java, '-Xmx2G', '-jar', str(jar), 'nogui'], cwd=directory,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
@@ -355,17 +371,18 @@ def uuid_fields(data_version, i):
return f'UUIDMost:{UUID_BASE << 32}L,UUIDLeast:{i + 1}L'
def build(version, force=False):
def build(name, force=False):
version, props = VARIANTS.get(name, (name, None))
jar, data_version, protocol = server_jar(version)
target = SHARED / 'worlds' / version
target = SHARED / 'worlds' / name
if target.exists() and not force:
print(f'{version}: kept {target}')
return []
era = scene.era(data_version)
work = LOCAL / 'gen' / version
work = LOCAL / 'gen' / name
shutil.rmtree(work, ignore_errors=True)
port = 30000 + (os.getpid() + data_version) % 20000
server = Server(work, jar, port, scene.experiments(data_version), java(version))
server = Server(work, jar, port, scene.experiments(data_version), java(version), props)
problems = []
def run(command):
@@ -375,19 +392,21 @@ def build(version, force=False):
problems.append((command[:160], bad[:2]))
return out
run('forceload add -48 -48 79 79')
run('fill 0 99 0 47 99 47 minecraft:stone')
run('fill 0 100 0 47 112 47 minecraft:air')
for i, (block, nbt) in enumerate(scene.block_entities(era, data_version)):
x, z = 1 + 3 * (i % 15), 1 + 3 * (i // 15)
run(f'setblock {x} 100 {z} minecraft:{block}{nbt}')
for i, (kind, extra) in enumerate(scene.entities(era, data_version)):
x, z = 1.5 + 3 * (i % 15), 25.5 + 3 * (i // 15)
fields = f'{uuid_fields(data_version, i)},NoAI:1b,Silent:1b,NoGravity:1b,Invulnerable:1b,PersistenceRequired:1b'
run(f'summon minecraft:{kind} {x} 100 {z} {{{fields}' + (',' + extra if extra else '') + '}')
for i, block in enumerate(scene.blocks(data_version)):
run(f'setblock {1 + 3 * (i % 15)} 100 {19 + 3 * (i // 15)} minecraft:{block}')
run('setworldspawn 20 101 20')
if not props: # variants only differ in their world type
run('forceload add -48 -48 79 79')
run('fill 0 99 0 47 99 47 minecraft:stone')
run('fill 0 100 0 47 112 47 minecraft:air')
for i, (block, nbt) in enumerate(scene.block_entities(era, data_version)):
x, z = 1 + 3 * (i % 15), 1 + 3 * (i // 15)
run(f'setblock {x} 100 {z} minecraft:{block}{nbt}')
for i, (kind, extra) in enumerate(scene.entities(era, data_version)):
x, z = 1.5 + 3 * (i % 15), 25.5 + 3 * (i // 15)
fields = f'{uuid_fields(data_version, i)},NoAI:1b,Silent:1b,NoGravity:1b,Invulnerable:1b,PersistenceRequired:1b'
run(f'summon minecraft:{kind} {x} 100 {z} {{{fields}' + (',' + extra if extra else '') + '}')
for i, block in enumerate(scene.blocks(data_version)):
run(f'setblock {1 + 3 * (i % 15)} 100 {19 + 3 * (i // 15)} minecraft:{block}')
if not props:
run('setworldspawn 20 101 20')
bot = Bot(port, protocol, BOT)
time.sleep(3)
for command in scene.player_commands(era, BOT, data_version):
@@ -418,7 +437,7 @@ def main():
p.add_argument('versions', nargs='*')
p.add_argument('--force', action='store_true')
args = p.parse_args()
for version in args.versions or VERSIONS:
for version in args.versions or [*VERSIONS, *VARIANTS]:
build(version, args.force)
+1 -1
View File
@@ -214,7 +214,7 @@ def main():
WORLDS.parent.mkdir(parents=True, exist_ok=True)
with open(WORLDS.parent / 'generate.lock', 'w') as lock:
fcntl.flock(lock, fcntl.LOCK_EX) # another worktree may be generating them
missing = [v for v in datafix_worlds.VERSIONS if not (WORLDS / v / 'level.dat').exists()]
missing = [v for v in [*datafix_worlds.VERSIONS, *datafix_worlds.VARIANTS] if not (WORLDS / v / 'level.dat').exists()]
if missing:
print('generating sample worlds:', ' '.join(missing), flush=True)
subprocess.run([sys.executable, str(ROOT / 'tools' / 'datafix_worlds.py'), *missing], cwd=ROOT, check=True)