Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Minecraft 1.20.2 #184

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ jobs:
]

steps:
- name: Select java runtime version
run: sudo update-java-alternatives --set $JAVA_HOME_21_X64
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: pip install pillow
run: pip install pillow pytest
- name: Pregenerate minecraft worlds
run: python -m tests.sample_server
- name: Run tests
run: python tests/alltests.py
run: pytest
5 changes: 1 addition & 4 deletions examples/biome_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Counter the number of biomes in the world. Works only for Anvil-based world folders.
"""
import locale, os, sys
from struct import pack, unpack

# local module
try:
Expand All @@ -14,9 +13,7 @@
if not os.path.exists(os.path.join(extrasearchpath,'nbt')):
raise
sys.path.append(extrasearchpath)
from nbt.region import RegionFile
from nbt.chunk import Chunk
from nbt.world import AnvilWorldFolder,UnknownWorldFormat
from nbt.world import AnvilWorldFolder

BIOMES = {
0 : "Ocean",
Expand Down
2 changes: 1 addition & 1 deletion examples/block_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def print_results():
print(locale.format_string("%20s: %12d", (block_id, block_count)))
block_total += block_count

solid_blocks = block_total - block_counts ['air']
solid_blocks = block_total - block_counts['minecraft:air']
solid_ratio = (solid_blocks+0.0)/block_total
print(locale.format_string("%d total blocks in world, %d are non-air (%0.4f", (block_total, solid_blocks, 100.0*solid_ratio))+"%)")

Expand Down
18 changes: 2 additions & 16 deletions examples/chest_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,7 @@ def chests_per_chunk(chunk):

chests = []

for entity in chunk['Entities']:
eid = entity["id"].value
if eid == "Minecart" and entity["type"].value == 1 or eid == "minecraft:chest_minecart":
x,y,z = entity["Pos"]
x,y,z = x.value,y.value,z.value

# Treasures are empty upon first opening

try:
items = items_from_nbt(entity["Items"])
except KeyError:
items = {}
chests.append(Chest("Minecart with chest",(x,y,z),items))

for entity in chunk['TileEntities']:
for entity in chunk['block_entities']:
eid = entity["id"].value
if eid == "Chest" or eid == "minecraft:chest":
x,y,z = entity["x"].value,entity["y"].value,entity["z"].value
Expand Down Expand Up @@ -103,7 +89,7 @@ def main(world_folder):

try:
for chunk in world.iter_nbt():
print_results(chests_per_chunk(chunk["Level"]))
print_results(chests_per_chunk(chunk))

except KeyboardInterrupt:
return 75 # EX_TEMPFAIL
Expand Down
9 changes: 6 additions & 3 deletions examples/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
if not os.path.exists(os.path.join(extrasearchpath,'nbt')):
raise
sys.path.append(extrasearchpath)
from nbt.region import RegionFile
from nbt.chunk import Chunk
from nbt.world import WorldFolder,McRegionWorldFolder
from nbt.world import WorldFolder
# PIL module (not build-in)
try:
from PIL import Image
Expand Down Expand Up @@ -73,6 +71,7 @@ def get_heightmap_image(chunk, buffer=False, gmin=False, gmax=False):
# 'redstone_ore', 'lapis_ore', 'emerald_ore',
# 'cobweb',
]
block_ignore = [f'minecraft:{b}' for b in block_ignore]


# Map of block colors from names
Expand Down Expand Up @@ -165,6 +164,10 @@ def get_heightmap_image(chunk, buffer=False, gmin=False, gmax=False):
'wheat': {'h':123, 's':60, 'l':50 },
'white_wool': {'h':0, 's':0, 'l':100},
}
block_colors = {
f'minecraft:{b}': c
for b, c in block_colors.items()
}


def get_map(chunk):
Expand Down
2 changes: 1 addition & 1 deletion examples/player_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Finds and prints different entities in a game file, including mobs, items, and vehicles.
"""

import locale, os, sys
import os, sys

# local module
try:
Expand Down
2 changes: 1 addition & 1 deletion examples/regionfile_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Defragment a given region file.
"""

import locale, os, sys
import os, sys
import collections
from optparse import OptionParser
import gzip
Expand Down
4 changes: 2 additions & 2 deletions examples/scoreboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
if not os.path.exists(os.path.join(extrasearchpath,'nbt')):
raise
sys.path.append(extrasearchpath)
from nbt.nbt import NBTFile, TAG_Long, TAG_Int, TAG_String, TAG_List, TAG_Compound
from nbt.nbt import NBTFile

def main(world_folder, show=True):
scorefile = world_folder + '/data/scoreboard.dat'
Expand All @@ -40,4 +40,4 @@ def main(world_folder, show=True):
print("No such folder as "+world_folder)
sys.exit(72) # EX_IOERR

sys.exit(main(world_folder, show))
sys.exit(main(world_folder, show))
2 changes: 1 addition & 1 deletion examples/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
def main(world_folder):
filename = os.path.join(world_folder,'level.dat')
level = NBTFile(filename)
print(level["Data"]["RandomSeed"])
print(level["Data"]["WorldGenSettings"]["seed"].value)
return 0 # NOERR


Expand Down
2 changes: 1 addition & 1 deletion examples/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
if not os.path.exists(os.path.join(extrasearchpath,'nbt')):
raise
sys.path.append(extrasearchpath)
from nbt.nbt import NBTFile, TAG_Long, TAG_Int, TAG_String, TAG_List, TAG_Compound
from nbt.nbt import TAG_Long, TAG_String, TAG_List, TAG_Compound

def unpack_nbt(tag):
"""
Expand Down
22 changes: 14 additions & 8 deletions nbt/chunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def block_id_to_name(bid):
class Chunk(object):
"""Class for representing a single chunk."""
def __init__(self, nbt):
self.chunk_data = nbt['Level']
self.chunk_data = nbt
self.coords = self.chunk_data['xPos'],self.chunk_data['zPos']

def get_coords(self):
Expand Down Expand Up @@ -152,7 +152,8 @@ def __init__(self, nbt, version):
elif version >= 2566 and version <= 2730: # MC 1.16.0 to MC 1.17.2 (latest tested version)
self._init_index_padded(nbt)
else:
raise NotImplementedError()
# best guess for other versions
self._init_index_padded(nbt)

# Section contains 4096 blocks whatever data version

Expand Down Expand Up @@ -228,12 +229,17 @@ def _init_index_unpadded(self, nbt):
# Contains palette of block names and indexes packed with padding if elements don't fit (post 1.16 format)

def _init_index_padded(self, nbt):

for p in nbt['Palette']:
for p in nbt['block_states']['palette']:
name = p['Name'].value
self.names.append(name)

states = nbt['BlockStates'].value
# When there is only one element in the palette and no data
# we have single block fill the whole section.
if len(self.names) == 1 and 'data' not in nbt['block_states']:
self.indexes = [0] * 4096
return

states = nbt['block_states']['data'].value
num_bits = (len(self.names) - 1).bit_length()
if num_bits < 4: num_bits = 4
mask = 2**num_bits - 1
Expand Down Expand Up @@ -294,9 +300,9 @@ def __init__(self, nbt):
# Load all sections

self.sections = {}
if 'Sections' in self.chunk_data:
for s in self.chunk_data['Sections']:
if "BlockStates" in s.keys() or "Blocks" in s.keys(): # sections may only contain lighting information
if 'sections' in self.chunk_data:
for s in self.chunk_data['sections']:
if 'block_states' in s.keys(): # sections may only contain lighting information
self.sections[s['Y'].value] = AnvilSection(s, version)


Expand Down
5 changes: 3 additions & 2 deletions nbt/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class RegionFile(object):
"""Constant indicating an normal status: the chunk does not exist.
Deprecated. Use :const:`nbt.region.STATUS_CHUNK_NOT_CREATED` instead."""

def __init__(self, filename=None, fileobj=None, chunkclass = None):
def __init__(self, filename=None, for_write=False, fileobj=None, chunkclass = None):
"""
Read a region file by filename or file object.
If a fileobj is specified, it is not closed after use; it is the callers responibility to close it.
Expand All @@ -206,7 +206,8 @@ def __init__(self, filename=None, fileobj=None, chunkclass = None):
self.chunkclass = chunkclass
if filename:
self.filename = filename
self.file = open(filename, 'r+b') # open for read and write in binary mode
mode = 'r+b' if for_write else 'rb'
self.file = open(filename, mode)
self._closefile = True
elif fileobj:
if hasattr(fileobj, 'name'):
Expand Down
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
python_files =
test_*.py
*tests.py
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[flake8]
ignore=
# line too long
E501,
# line breaks around binary operator
W503,W504,
3 changes: 2 additions & 1 deletion tests/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Sample World
Sample_World.tar.gz
Sample_World.tar.gz
sample_server
Empty file added tests/__init__.py
Empty file.
56 changes: 0 additions & 56 deletions tests/alltests.py

This file was deleted.

Loading