Fix biomes for Minecraft 1.15
Fixes #1650. Mojang changed the biomes code so that it now can have different biomes for different Y levels. We need to adjust our logic accordingly, which is done through some small BiomeDispensary class where we shove a numpy'd Mojang array in and can then read out the biomes for each level. Biome data is now stored per-section, which needed some changes on the C side of things. I didn't change anything in the biome overlay code so I wouldn't be surprised if it's broken now, but for the time being I'd rather have 1.15 fixed than some obscure overlay. Tested to work with 1.14 and 1.15 data. No new biomes have been added to the code yet.
This commit is contained in:
40
overviewer_core/biome.py
Normal file
40
overviewer_core/biome.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# This file is part of the Minecraft Overviewer.
|
||||||
|
#
|
||||||
|
# Minecraft Overviewer is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
# your option) any later version.
|
||||||
|
#
|
||||||
|
# Minecraft Overviewer is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details, and note that Jeffrey Epstein didn't kill
|
||||||
|
# himself.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
class BiomeDispensary:
|
||||||
|
"""Turns biome arrays of either 256 or 1024 integer values into 16x16 2d arrays,
|
||||||
|
which can then be retrieved for any Y level with get_biome.
|
||||||
|
"""
|
||||||
|
def __init__(self, biome_array):
|
||||||
|
self.biome_len = len(biome_array)
|
||||||
|
if self.biome_len == 256:
|
||||||
|
self.biomes = [biome_array.reshape((16, 16))]
|
||||||
|
elif self.biome_len == 1024:
|
||||||
|
self.biomes = [None] * 4
|
||||||
|
for i in range(0, 4):
|
||||||
|
# Map 256 values of the array to each self.biomes entry, resulting
|
||||||
|
# in 4 entries
|
||||||
|
self.biomes[i] = biome_array[i * 256:(i + 1) * 256].reshape((16, 16))
|
||||||
|
|
||||||
|
def get_biome(self, y_level):
|
||||||
|
if y_level < 0:
|
||||||
|
return None
|
||||||
|
if self.biome_len == 256:
|
||||||
|
return self.biomes[0]
|
||||||
|
else:
|
||||||
|
return self.biomes[y_level // 4]
|
||||||
@@ -109,10 +109,12 @@ static inline void load_chunk_section(ChunkData* dest, int32_t i, PyObject* sect
|
|||||||
dest->sections[i].data = (PyArrayObject*)PyDict_GetItemString(section, "Data");
|
dest->sections[i].data = (PyArrayObject*)PyDict_GetItemString(section, "Data");
|
||||||
dest->sections[i].skylight = (PyArrayObject*)PyDict_GetItemString(section, "SkyLight");
|
dest->sections[i].skylight = (PyArrayObject*)PyDict_GetItemString(section, "SkyLight");
|
||||||
dest->sections[i].blocklight = (PyArrayObject*)PyDict_GetItemString(section, "BlockLight");
|
dest->sections[i].blocklight = (PyArrayObject*)PyDict_GetItemString(section, "BlockLight");
|
||||||
|
dest->sections[i].biomes = (PyArrayObject*)PyDict_GetItemString(section, "Biomes");
|
||||||
Py_INCREF(dest->sections[i].blocks);
|
Py_INCREF(dest->sections[i].blocks);
|
||||||
Py_INCREF(dest->sections[i].data);
|
Py_INCREF(dest->sections[i].data);
|
||||||
Py_INCREF(dest->sections[i].skylight);
|
Py_INCREF(dest->sections[i].skylight);
|
||||||
Py_INCREF(dest->sections[i].blocklight);
|
Py_INCREF(dest->sections[i].blocklight);
|
||||||
|
Py_INCREF(dest->sections[i].biomes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* loads the given chunk into the chunks[] array in the state
|
/* loads the given chunk into the chunks[] array in the state
|
||||||
@@ -130,13 +132,12 @@ bool load_chunk(RenderState* state, int32_t x, int32_t z, uint8_t required) {
|
|||||||
if (dest->loaded)
|
if (dest->loaded)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* set up reasonable defaults */
|
|
||||||
dest->biomes = NULL;
|
|
||||||
for (i = 0; i < SECTIONS_PER_CHUNK; i++) {
|
for (i = 0; i < SECTIONS_PER_CHUNK; i++) {
|
||||||
dest->sections[i].blocks = NULL;
|
dest->sections[i].blocks = NULL;
|
||||||
dest->sections[i].data = NULL;
|
dest->sections[i].data = NULL;
|
||||||
dest->sections[i].skylight = NULL;
|
dest->sections[i].skylight = NULL;
|
||||||
dest->sections[i].blocklight = NULL;
|
dest->sections[i].blocklight = NULL;
|
||||||
|
dest->sections[i].biomes = NULL;
|
||||||
}
|
}
|
||||||
dest->loaded = 1;
|
dest->loaded = 1;
|
||||||
|
|
||||||
@@ -166,8 +167,6 @@ bool load_chunk(RenderState* state, int32_t x, int32_t z, uint8_t required) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->biomes = (PyArrayObject*)PyDict_GetItemString(chunk, "Biomes");
|
|
||||||
Py_INCREF(dest->biomes);
|
|
||||||
|
|
||||||
for (i = 0; i < PySequence_Fast_GET_SIZE(sections); i++) {
|
for (i = 0; i < PySequence_Fast_GET_SIZE(sections); i++) {
|
||||||
PyObject* ycoord = NULL;
|
PyObject* ycoord = NULL;
|
||||||
@@ -194,12 +193,12 @@ unload_all_chunks(RenderState* state) {
|
|||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
for (j = 0; j < 3; j++) {
|
for (j = 0; j < 3; j++) {
|
||||||
if (state->chunks[i][j].loaded) {
|
if (state->chunks[i][j].loaded) {
|
||||||
Py_XDECREF(state->chunks[i][j].biomes);
|
|
||||||
for (k = 0; k < SECTIONS_PER_CHUNK; k++) {
|
for (k = 0; k < SECTIONS_PER_CHUNK; k++) {
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].blocks);
|
Py_XDECREF(state->chunks[i][j].sections[k].blocks);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].data);
|
Py_XDECREF(state->chunks[i][j].sections[k].data);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].skylight);
|
Py_XDECREF(state->chunks[i][j].sections[k].skylight);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].blocklight);
|
Py_XDECREF(state->chunks[i][j].sections[k].blocklight);
|
||||||
|
Py_XDECREF(state->chunks[i][j].sections[k].biomes);
|
||||||
}
|
}
|
||||||
state->chunks[i][j].loaded = 0;
|
state->chunks[i][j].loaded = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
// increment this value if you've made a change to the c extesion
|
// increment this value if you've made a change to the c extesion
|
||||||
// and want to force users to rebuild
|
// and want to force users to rebuild
|
||||||
#define OVERVIEWER_EXTENSION_VERSION 77
|
#define OVERVIEWER_EXTENSION_VERSION 78
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -87,7 +87,7 @@ typedef struct {
|
|||||||
/* all the sections in a given chunk */
|
/* all the sections in a given chunk */
|
||||||
struct {
|
struct {
|
||||||
/* all there is to know about each section */
|
/* all there is to know about each section */
|
||||||
PyArrayObject *blocks, *data, *skylight, *blocklight;
|
PyArrayObject *blocks, *data, *skylight, *blocklight, *biomes;
|
||||||
} sections[SECTIONS_PER_CHUNK];
|
} sections[SECTIONS_PER_CHUNK];
|
||||||
} ChunkData;
|
} ChunkData;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -210,7 +210,7 @@ static inline uint32_t get_data(RenderState* state, DataType type, int32_t x, in
|
|||||||
data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight;
|
data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight;
|
||||||
break;
|
break;
|
||||||
case BIOMES:
|
case BIOMES:
|
||||||
data_array = state->chunks[chunkx][chunkz].biomes;
|
data_array = state->chunks[chunkx][chunkz].sections[chunky].biomes;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data_array == NULL)
|
if (data_array == NULL)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import numpy
|
|||||||
|
|
||||||
from . import nbt
|
from . import nbt
|
||||||
from . import cache
|
from . import cache
|
||||||
|
from .biome import BiomeDispensary
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines for extracting information about available worlds
|
This module has routines for extracting information about available worlds
|
||||||
@@ -1370,13 +1371,15 @@ class RegionSet(object):
|
|||||||
biomes = numpy.frombuffer(biomes, dtype=numpy.uint8)
|
biomes = numpy.frombuffer(biomes, dtype=numpy.uint8)
|
||||||
else:
|
else:
|
||||||
biomes = numpy.asarray(biomes)
|
biomes = numpy.asarray(biomes)
|
||||||
biomes = biomes.reshape((16,16))
|
#biomes = biomes.reshape((16,16))
|
||||||
|
biome_giver = BiomeDispensary(biomes)
|
||||||
else:
|
else:
|
||||||
# Worlds converted by Jeb's program may be missing the Biomes key.
|
# Worlds converted by Jeb's program may be missing the Biomes key.
|
||||||
# Additionally, 19w09a worlds have an empty array as biomes key
|
# Additionally, 19w09a worlds have an empty array as biomes key
|
||||||
# in some cases.
|
# in some cases.
|
||||||
biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
|
#biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
|
||||||
chunk_data['Biomes'] = biomes
|
biome_giver = BiomeDispensary(numpy.zeros(256, dtype=numpy.uint8))
|
||||||
|
#chunk_data['Biomes'] = biomes
|
||||||
|
|
||||||
unrecognized_block_types = {}
|
unrecognized_block_types = {}
|
||||||
for section in chunk_data['Sections']:
|
for section in chunk_data['Sections']:
|
||||||
@@ -1384,6 +1387,7 @@ class RegionSet(object):
|
|||||||
# Turn the skylight array into a 16x16x16 matrix. The array comes
|
# Turn the skylight array into a 16x16x16 matrix. The array comes
|
||||||
# packed 2 elements per byte, so we need to expand it.
|
# packed 2 elements per byte, so we need to expand it.
|
||||||
try:
|
try:
|
||||||
|
section['Biomes'] = biome_giver.get_biome(section["Y"])
|
||||||
if 'SkyLight' in section:
|
if 'SkyLight' in section:
|
||||||
skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
|
skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
|
||||||
skylight = skylight.reshape((16,16,8))
|
skylight = skylight.reshape((16,16,8))
|
||||||
@@ -1607,6 +1611,9 @@ class RotatedRegionSet(RegionSetWrapper):
|
|||||||
for section in chunk_data['Sections']:
|
for section in chunk_data['Sections']:
|
||||||
section = dict(section)
|
section = dict(section)
|
||||||
newsections.append(section)
|
newsections.append(section)
|
||||||
|
biomes = numpy.swapaxes(section['Biomes'], 0, 1)
|
||||||
|
biomes = numpy.rot90(biomes, self.north_dir)
|
||||||
|
section['Biomes'] = numpy.swapaxes(biomes, 0, 1)
|
||||||
for arrayname in ['Blocks', 'Data', 'SkyLight', 'BlockLight']:
|
for arrayname in ['Blocks', 'Data', 'SkyLight', 'BlockLight']:
|
||||||
array = section[arrayname]
|
array = section[arrayname]
|
||||||
# Since the anvil change, arrays are arranged with axes Y,Z,X
|
# Since the anvil change, arrays are arranged with axes Y,Z,X
|
||||||
@@ -1619,9 +1626,6 @@ class RotatedRegionSet(RegionSetWrapper):
|
|||||||
chunk_data['Sections'] = newsections
|
chunk_data['Sections'] = newsections
|
||||||
|
|
||||||
# same as above, for biomes (Z/X indexed)
|
# same as above, for biomes (Z/X indexed)
|
||||||
biomes = numpy.swapaxes(chunk_data['Biomes'], 0, 1)
|
|
||||||
biomes = numpy.rot90(biomes, self.north_dir)
|
|
||||||
chunk_data['Biomes'] = numpy.swapaxes(biomes, 0, 1)
|
|
||||||
return chunk_data
|
return chunk_data
|
||||||
|
|
||||||
def get_chunk_mtime(self, x, z):
|
def get_chunk_mtime(self, x, z):
|
||||||
|
|||||||
Reference in New Issue
Block a user