few additions and bug fixes

Added Hand Sway (not an option yet) from b1,8

Fire works properly in multiplayer now and also renders on undead mobs in sunlight now

hopefully fixed bow stuck bug on touchscreen devices

Fire on entites uses updated rendering from b1.6.6

Shadow bug fix attempt the 15th

Added looking at block in debug screen

Zombie Pigman now renders his hat layer

Temporarily disabled most randomlevelsource cave, canyon and tall grass code just for this commit to test performance
This commit is contained in:
Shredder
2026-05-23 05:29:47 +05:00
parent 4b69a1240a
commit d7d887f882
17 changed files with 287 additions and 120 deletions

View File

@@ -857,6 +857,7 @@ void Minecraft::tickInput() {
for (int i = 0; i < 5 * SharedConstants::TicksPerSecond; ++i)
level->tick();
}
#endif
}
@@ -882,6 +883,9 @@ void Minecraft::tickInput() {
handleBuildAction(&bai);
} else {
gameMode->stopDestroyBlock();
if (player && player->isUsingItem()) {
gameMode->releaseUsingItem(player);
}
}
} else {
// Desktop: left mouse = destroy/attack

View File

@@ -224,58 +224,58 @@ void Gui::handleClick(int button, int x, int y) {
}
}
void Gui::handleKeyPressed(int key)
{
bool isChatting = (minecraft->screen && (dynamic_cast<ChatScreen*>(minecraft->screen) || dynamic_cast<ConsoleScreen*>(minecraft->screen)));
if (isChatting) {
// Allow scrolling the chat history with the mouse/keyboard when chat is open
if (key == 38) { // VK_UP
scrollChat(1);
return;
} else if (key == 40) { // VK_DOWN
scrollChat(-1);
return;
} else if (key == 33) { // VK_PRIOR (Page Up)
// Scroll by a page
int screenHeight = (int)(minecraft->height * InvGuiScale);
int maxVisible = (screenHeight - 48) / 9;
scrollChat(maxVisible);
return;
} else if (key == 34) { // VK_NEXT (Page Down)
int screenHeight = (int)(minecraft->height * InvGuiScale);
int maxVisible = (screenHeight - 48) / 9;
scrollChat(-maxVisible);
return;
void Gui::handleKeyPressed(int key)
{
bool isChatting = (minecraft->screen && (dynamic_cast<ChatScreen*>(minecraft->screen) || dynamic_cast<ConsoleScreen*>(minecraft->screen)));
if (isChatting) {
// Allow scrolling the chat history with the mouse/keyboard when chat is open
if (key == 38) { // VK_UP
scrollChat(1);
return;
} else if (key == 40) { // VK_DOWN
scrollChat(-1);
return;
} else if (key == 33) { // VK_PRIOR (Page Up)
// Scroll by a page
int screenHeight = (int)(minecraft->height * InvGuiScale);
int maxVisible = (screenHeight - 48) / 9;
scrollChat(maxVisible);
return;
} else if (key == 34) { // VK_NEXT (Page Down)
int screenHeight = (int)(minecraft->height * InvGuiScale);
int maxVisible = (screenHeight - 48) / 9;
scrollChat(-maxVisible);
return;
}
}
}
if (key == Keyboard::KEY_F1) {
minecraft->options.toggle(OPTIONS_HIDEGUI);
}
if (key == Keyboard::KEY_F1) {
minecraft->options.toggle(OPTIONS_HIDEGUI);
}
if (key == 99)
{
if (minecraft->player->inventory->selected > 0)
if (key == 99)
{
minecraft->player->inventory->selected--;
if (minecraft->player->inventory->selected > 0)
{
minecraft->player->inventory->selected--;
}
}
else if (key == 4)
{
if (minecraft->player->inventory->selected < (getNumSlots() - 2))
{
minecraft->player->inventory->selected++;
}
}
else if (key == 100)
{
minecraft->screenChooser.setScreen(SCREEN_BLOCKSELECTION);
}
else if (key == minecraft->options.getIntValue(OPTIONS_KEY_DROP))
{
minecraft->player->inventory->dropSlot(minecraft->player->inventory->selected, false);
}
}
else if (key == 4)
{
if (minecraft->player->inventory->selected < (getNumSlots() - 2))
{
minecraft->player->inventory->selected++;
}
}
else if (key == 100)
{
minecraft->screenChooser.setScreen(SCREEN_BLOCKSELECTION);
}
else if (key == minecraft->options.getIntValue(OPTIONS_KEY_DROP))
{
minecraft->player->inventory->dropSlot(minecraft->player->inventory->selected, false);
}
}
void Gui::scrollChat(int delta) {
if (delta == 0)
@@ -782,6 +782,25 @@ void Gui::renderDebugInfo() {
long day = worldTime / Level::TICKS_PER_DAY;
long seed = lvl ? lvl->getSeed() : 0;
// Block looking at
std::string CurrentTile = "Air";
if (minecraft->hitResult.type == TILE) {
int LookingX = minecraft->hitResult.x;
int LookingY = minecraft->hitResult.y;
int LookingZ = minecraft->hitResult.z;
int tileID = minecraft->level->getTile(LookingX, LookingY, LookingZ);
if (tileID > 0 && tileID < 256){
Tile* LookingTile = Tile::tiles[tileID];
if (LookingTile != NULL) {
CurrentTile = LookingTile->getDescriptionId();
} else {
CurrentTile = "Unknown Tile";
}
} else {
CurrentTile = "Air";
}
}
// Build lines (NULL entry = blank gap)
Font* font = minecraft->font;
@@ -820,6 +839,9 @@ void Gui::renderDebugInfo() {
sprintf(buf, "Biome: %s", biomeName);
drawString(font, buf, 2, 124, 0xE0E0E0);
sprintf(buf, "Looking at: %s", CurrentTile.c_str());
drawString(font, buf, 2, 134, 0xE0E0E0);
}
else if (minecraft->options.getIntValue(OPTIONS_DEBUG_STYLE) == 1){

View File

@@ -40,10 +40,9 @@ HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/, int texW /
head.addBox(-4, -8, -4, 8, 8, 8, g); // Head
head.setPos(0, 0 + yOffset, 0);
if (modernSkin) {
hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Outer head layer (hat)
hair.setPos(0, 0 + yOffset, 0);
}
hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Outer head layer (hat)
hair.setPos(0, 0 + yOffset, 0);
body.addBox(-4, 0, -2, 8, 12, 4, g); // Body
body.setPos(0, 0 + yOffset, 0);
@@ -99,17 +98,17 @@ void HumanoidModel::render(Entity* e, float time, float r, float bob, float yRot
setupAnim(time, r, bob, yRot, xRot, scale);
// Sync overlay with head rotation/position
if (texWidth == 64 && texHeight == 64) {
hair.xRot = head.xRot;
hair.yRot = head.yRot;
hair.zRot = head.zRot;
hair.y = head.y;
}
hair.xRot = head.xRot;
hair.yRot = head.yRot;
hair.zRot = head.zRot;
hair.y = head.y;
head.render(scale);
if (texWidth == 64 && texHeight == 64) {
hair.render(scale);
}
hair.render(scale);
body.render(scale);
arm0.render(scale);
arm1.render(scale);
@@ -123,12 +122,12 @@ void HumanoidModel::render( HumanoidModel* model, float scale )
head.yRot = model->head.yRot;
head.y = model->head.y;
head.xRot = model->head.xRot;
if (texWidth == 64 && texHeight == 64) {
hair.yRot = head.yRot;
hair.xRot = head.xRot;
hair.zRot = head.zRot;
hair.y = head.y;
}
hair.yRot = head.yRot;
hair.xRot = head.xRot;
hair.zRot = head.zRot;
hair.y = head.y;
arm0.xRot = model->arm0.xRot;
arm0.zRot = model->arm0.zRot;
@@ -273,7 +272,7 @@ void HumanoidModel::onGraphicsReset()
arm1.onGraphicsReset();
leg0.onGraphicsReset();
leg1.onGraphicsReset();
//hair.onGraphicsReset();
hair.onGraphicsReset();
}
//void renderHair(float scale) {

View File

@@ -355,7 +355,11 @@ LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, const std::string&
armorTypeHash(0),
sprinting(false),
sprintDoubleTapTimer(0),
prevForwardHeld(false)
prevForwardHeld(false),
xBob(0.0f),
yBob(0.0f),
xBobO(0.0f),
yBobO(0.0f)
{
this->dimension = dimension;
_init();
@@ -550,6 +554,10 @@ void LocalPlayer::aiStep() {
//if (onGround && abilities.flying)
// abilities.flying = false;
yBobO = yBob;
xBobO = xBob;
xBob += (xRot - xBob) * 0.5;
yBob += (yRot - yBob) * 0.5;
if (interpolateOnly())
updateAi();

View File

@@ -22,6 +22,10 @@ public:
virtual void reset();
void tick();
float yBob, xBob; // shredder added from b1.8/4j for the hand swaying animation
float yBobO, xBobO; // shredder added from b1.8/4j for the hand swaying animation
void move(float xa, float ya, float za);
void aiStep();

View File

@@ -347,6 +347,7 @@ void GameRenderer::renderLevel(float a) {
Lighting::turnOff();
glDisable2(GL_BLEND);
glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Lighting::turnOff();
setupFog(0);
glEnable2(GL_BLEND);
glDisable2(GL_CULL_FACE);

View File

@@ -280,6 +280,9 @@ void ItemInHandRenderer::render( float a )
float h = oHeight + (height - oHeight) * a;
Player* player = mc->player;
// if (selectedTile==NULL) return;
LocalPlayer* localPlayer = (LocalPlayer*)(player);
float xr = player->xRotO + (player->xRot - player->xRotO) * a; // shredder added for hand swaying
glPushMatrix2();
glRotatef2(player->xRotO + (player->xRot - player->xRotO) * a, 1, 0, 0);
@@ -288,6 +291,16 @@ void ItemInHandRenderer::render( float a )
Lighting::turnOn(mc);
glPopMatrix2();
if (localPlayer) // shredder added, basically does the hand swaying animation from b1.8
{
float xrr = localPlayer->xBobO + (localPlayer->xBob - localPlayer->xBobO) * a;
float yrr = localPlayer->yBobO + (localPlayer->yBob - localPlayer->yBobO) * a;
// 4J - was using player->xRot and yRot directly here rather than interpolating between old & current with a
float yr = player->yRotO + (player->yRot - player->yRotO) * a;
glRotatef((xr - xrr) * 0.1f, 1, 0, 0);
glRotatef((yr - yrr) * 0.1f, 0, 1, 0);
}
float br = mc->level->getBrightness(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
ItemInstance* item;// = selectedItem;

View File

@@ -133,6 +133,8 @@ void EntityRenderer::postRender(Entity* entity, float x, float y, float z, float
}
if (entity->isOnFire()) renderFlame(entity, x, y, z, a);
}
// duplicate renderflame below is the pre b1.6.6 way flame rendered on player and mobs, probs gonna make it an option. - shredder
/*
void EntityRenderer::renderFlame(Entity* e, float x, float y, float z, float a) {
glDisable(GL_LIGHTING);
int tex = ((Tile*)Tile::fire)->tex;
@@ -178,6 +180,75 @@ void EntityRenderer::renderFlame(Entity* e, float x, float y, float z, float a)
glPopMatrix2();
glEnable2(GL_LIGHTING);
}
*/
// new renderflame ported from b1.6.6, fixes player height offset and also uses the newer style - shredder
void EntityRenderer::renderFlame(Entity* e, float x, float y, float z, float a) {
glDisable(GL_LIGHTING);
int tex = ((Tile*)Tile::fire)->tex;
int xt = (tex & 0xf) << 4;
int yt = tex & 0xf0;
float u0 = (xt) / 256.0f;
float u1 = (xt + 15.99f) / 256.0f;
float v0 = (yt) / 256.0f;
float v1 = (yt + 15.99f) / 256.0f;
glPushMatrix2();
glTranslatef2((float) x, (float) y, (float) z);
float s = e->bbWidth * 1.4f;
glScalef2(s, s, s);
bindTexture("terrain.png");
Tesselator& t = Tesselator::instance;
float r = 0.5f;
float xo = 0.0f;
float h = e->bbHeight / s;
float yo = (float)(e->y - e->bb.y0) / s;
glRotatef2(-entityRenderDispatcher->playerRotY, 0, 1, 0);
glTranslatef2(0, 0, -0.3f + ((int) h) * 0.02f);
glColor4f2(1, 1, 1, 1);
float zo = 0.0f;
int cycle_counter = 0;
t.begin();
while (h > 0.0f) {
float current_u0 = u0;
float current_u1 = u1;
float current_v0 = v0;
float current_v1 = v1;
if (cycle_counter % 2 != 0) {
current_v0 = (yt + 16) / 256.0f;
current_v1 = (yt + 16 + 15.99f) / 256.0f;
}
if ((cycle_counter / 2) % 2 == 0) {
float tmp = current_u1;
current_u1 = current_u0;
current_u0 = tmp;
}
t.vertexUV(r - xo, 0.0f - yo, zo, current_u1, current_v1);
t.vertexUV(-r - xo, 0.0f - yo, zo, current_u0, current_v1);
t.vertexUV(-r - xo, 1.4f - yo, zo, current_u0, current_v0);
t.vertexUV(r - xo, 1.4f - yo, zo, current_u1, current_v0);
h -= 0.45f;
yo -= 0.45f;
r *= 0.9f;
zo += 0.03f;
cycle_counter++;
}
t.draw();
glPopMatrix2();
glEnable2(GL_LIGHTING);
}
void EntityRenderer::renderShadow(Entity* e, float x, float y, float z, float pow, float a) { //
glEnable2(GL_BLEND);
@@ -229,6 +300,8 @@ void EntityRenderer::renderShadow(Entity* e, float x, float y, float z, float po
glColor4f2(1, 1, 1, 1);
glDisable2(GL_BLEND);
glDepthMask(true);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
Level* EntityRenderer::getLevel() {

View File

@@ -218,6 +218,8 @@ void MobRenderer::renderNameTag(Mob* mob, const std::string& name, float x, floa
glScalef2(-s, -s, s);
glDisable(GL_LIGHTING);
glDepthMask(false);
glDisable2(GL_DEPTH_TEST);
glEnable2(GL_BLEND);
@@ -241,6 +243,7 @@ void MobRenderer::renderNameTag(Mob* mob, const std::string& name, float x, floa
glDepthMask(true);
font->draw(name, (float) fnameWidth, 0, 0xffffffff);
glEnable(GL_LIGHTING);
glDisable2(GL_BLEND);
glColor4f2(1, 1, 1, 1);
glPopMatrix2();

View File

@@ -465,6 +465,15 @@ void Entity::tick()
baseTick();
}
void Entity::setOnFire(int numberOfSeconds)
{
int newValue = numberOfSeconds * 20;
if (onFire < newValue)
{
onFire = newValue;
}
}
void Entity::baseTick()
{
TIMER_PUSH("entityBaseTick");

View File

@@ -89,6 +89,8 @@ public:
virtual bool isAlive();
virtual bool isOnFire();
virtual void setOnFire(int numberOfSeconds);
virtual bool isPlayer();
virtual bool isCreativeModeAllowed();

View File

@@ -169,7 +169,9 @@ void Mob::playAmbientSound()
level->playSound(this, ambient, getSoundVolume(), getVoicePitch());
}
}
bool Mob::isOnFire() {
return onFire > 0 || getSharedFlag(SharedFlagsInformation::FLAG_ONFIRE);
}
void Mob::baseTick()
{
oAttackAnim = attackAnim;
@@ -177,6 +179,18 @@ void Mob::baseTick()
TIMER_PUSH("mobBaseTick");
// @TODO - check if this section i added to make fire render in mp doesnt break crap - shredder
if (!level->isClientSide) {
// Server side: Tell the client if this mob/player is still burning
setSharedFlag(SharedFlagsInformation::FLAG_ONFIRE, onFire > 0);
} else {
// Client side: If the server flag says we aren't on fire, extinguish local visual timer
if (!getSharedFlag(SharedFlagsInformation::FLAG_ONFIRE)) {
onFire = 0;
}
}
// todo ends here
if (((ambientSoundTime++ & 15) == 0) && random.nextInt(2000) < ambientSoundTime) {
ambientSoundTime = -getAmbientSoundInterval();
playAmbientSound();

View File

@@ -52,6 +52,7 @@ public:
virtual bool isPickable();
virtual bool isPushable();
virtual bool isOnFire();
virtual bool isShootable();
MoveControl* getMoveControl();

View File

@@ -19,7 +19,7 @@ void Skeleton::aiStep() {
float br = getBrightness(1);
if (br > 0.5f) {
if (level->canSeeSky(Mth::floor(x), Mth::floor(y), Mth::floor(z)) && random.nextFloat() * 3.5f < (br - 0.4f)) {
hurt(NULL, 1);
// hurt(NULL, 1); // no use anymore since i restored setOnFire - shredder
for (int i = 0; i < 5; ++i) {
float xa = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
@@ -27,7 +27,7 @@ void Skeleton::aiStep() {
float za = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
level->addParticle(PARTICLETYPE(explode), x + random.nextFloat() * bbWidth * 2 - bbWidth, y + random.nextFloat() * bbHeight, z + random.nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za);
}
//setOnFire(8); //@todo
setOnFire(8); //@todo
}
}
}

View File

@@ -55,7 +55,7 @@ void Zombie::aiStep() {
float br = getBrightness(1);
if (br > 0.5f) {
if (level->canSeeSky(Mth::floor(x), Mth::floor(y), Mth::floor(z)) && random.nextFloat() * 3.5f < (br - 0.4f)) {
hurt(NULL, 1);
// hurt(NULL, 1); // no use anymore since i restored setOnFire - shredder
for (int i = 0; i < 5; ++i) {
float xa = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
@@ -63,7 +63,7 @@ void Zombie::aiStep() {
float za = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
level->addParticle(PARTICLETYPE(explode), x + random.nextFloat() * bbWidth * 2 - bbWidth, y + random.nextFloat() * bbHeight, z + random.nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za);
}
//setOnFire(8); //@todo
setOnFire(8); //@todo
}
}
}

View File

@@ -216,6 +216,7 @@ bool Player::checkBed() {
return (level->getTile(bedPosition.x, bedPosition.y, bedPosition.z) == Tile::bed->id);
}
void Player::tick() {
bool shouldSleep = entityData.getFlag<SharedFlagsInformation::SharedFlagsInformationType>(DATA_PLAYER_FLAGS_ID, PLAYER_SLEEP_FLAG);
if(shouldSleep != isSleeping()) {
@@ -246,11 +247,24 @@ void Player::tick() {
}
super::tick();
if (!level->isClientSide) {
// @TODO - Awful Hack Start, MCPE 0.6 doesn't deal damage with fire to players for some reason on mp, so we force it here to do it, need a better way honestly - shredder
if (level->containsFireTile(this->bb)) {
hurt(NULL, 1);
if (!isInWater()) {
if (onFire <= 0) {
onFire = 20 * 15;
}
}
}
// Awful hack ends here
foodData.tick(this);
// if (containerMenu != NULL && !containerMenu->stillValid(this)) {
// closeContainer();
// }
// if (containerMenu != NULL && !containerMenu->stillValid(this)) {
// closeContainer();
// }
}
}

View File

@@ -236,26 +236,26 @@ void RandomLevelSource::postProcess(ChunkSource* parent, int xt, int zt) {
// @todo - add generation options to enable or disable extra features like lava lakes or water lakes as they affect seed parity with the original vanilla game - shredder
// //@todo: hide those chunks if they are aren't visible
if (random.nextInt(4) == 0) {
int x = xo + random.nextInt(16) + 8;
int y = random.nextInt(128);
int z = zo + random.nextInt(16) + 8;
LakeFeature feature(Tile::calmWater->id);
feature.place(level, &random, x, y, z);
// LOGI("Adding underground lake @ (%d,%d,%d)\n", x, y, z);
}
//// //@todo: hide those chunks if they are aren't visible
//if (random.nextInt(4) == 0) {
// int x = xo + random.nextInt(16) + 8;
// int y = random.nextInt(128);
// int z = zo + random.nextInt(16) + 8;
// LakeFeature feature(Tile::calmWater->id);
// feature.place(level, &random, x, y, z);
// // LOGI("Adding underground lake @ (%d,%d,%d)\n", x, y, z);
//}
////@todo: hide those chunks if they are aren't visible
if (random.nextInt(8) == 0) {
int x = xo + random.nextInt(16) + 8;
int y = random.nextInt(random.nextInt(120) + 8);
int z = zo + random.nextInt(16) + 8;
if (y < 64 || random.nextInt(10) == 0) {
LakeFeature feature(Tile::calmLava->id);
feature.place(level, &random, x, y, z);
}
}
//////@todo: hide those chunks if they are aren't visible
//if (random.nextInt(8) == 0) {
// int x = xo + random.nextInt(16) + 8;
// int y = random.nextInt(random.nextInt(120) + 8);
// int z = zo + random.nextInt(16) + 8;
// if (y < 64 || random.nextInt(10) == 0) {
// LakeFeature feature(Tile::calmLava->id);
// feature.place(level, &random, x, y, z);
// }
//}
static float totalTime = 0;
const float st = getTimeS();
@@ -434,30 +434,30 @@ void RandomLevelSource::postProcess(ChunkSource* parent, int xt, int zt) {
//}
// reworked code from above to generate ferns and shrubs to just like in beta java
int grassCount = 0;
if (biome == Biome::forest) { grassCount = 2; }
else if (biome == Biome::rainForest) { grassCount = 10; }
else if (biome == Biome::seasonalForest) { grassCount = 2; }
else if (biome == Biome::taiga) { grassCount = 1; }
else if (biome == Biome::plains) { grassCount = 10; }
for (int i = 0; i < grassCount; i++) {
int grassMetadata = TallGrass::TALL_GRASS;
if (biome == Biome::rainForest && random.nextInt(3) != 0) {
grassMetadata = TallGrass::FERN;
}
int x = xo + random.nextInt(16) + 8;
int z = zo + random.nextInt(16) + 8;
int y = level->getHeightmap(x, z);
TallgrassFeature grassFeature(Tile::tallgrass->id, grassMetadata);
grassFeature.place(level, &random, x, y, z);
}
//int grassCount = 0;
//
//if (biome == Biome::forest) { grassCount = 2; }
//else if (biome == Biome::rainForest) { grassCount = 10; }
//else if (biome == Biome::seasonalForest) { grassCount = 2; }
//else if (biome == Biome::taiga) { grassCount = 1; }
//else if (biome == Biome::plains) { grassCount = 10; }
//
//for (int i = 0; i < grassCount; i++) {
// int grassMetadata = TallGrass::TALL_GRASS;
//
//
// if (biome == Biome::rainForest && random.nextInt(3) != 0) {
// grassMetadata = TallGrass::FERN;
// }
//
// int x = xo + random.nextInt(16) + 8;
//
// int z = zo + random.nextInt(16) + 8;
// int y = level->getHeightmap(x, z);
//
// TallgrassFeature grassFeature(Tile::tallgrass->id, grassMetadata);
// grassFeature.place(level, &random, x, y, z);
//}
for (int i = 0; i < 10; i++) {
@@ -557,8 +557,8 @@ LevelChunk* RandomLevelSource::getChunk(int xOffs, int zOffs) {
buildSurfaces(xOffs, zOffs, blocks, biomes);
// Carve caves into the chunk
caveFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount);
canyonFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount);
// caveFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount);
// canyonFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount);
levelChunk->recalcHeightmap();
return levelChunk;