Skip to content
Open
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
66 changes: 48 additions & 18 deletions chunky/src/java/se/llbit/chunky/block/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import se.llbit.nbt.CompoundTag;
import se.llbit.nbt.Tag;

import java.util.Collection;
import java.util.Random;

public abstract class Block extends Material {
Expand All @@ -25,13 +26,6 @@ public abstract class Block extends Material {
*/
public boolean localIntersect = false;

/**
* Invisible blocks are not rendered as regular voxels (they are not added to the voxel octree).
* This is used for blocks that are rendered as entities, and blocks that are not implemented
* yet.
*/
public boolean invisible = false;

public Block(String name, Texture texture) {
super(name, texture);
}
Expand Down Expand Up @@ -97,31 +91,67 @@ public String toString() {
return name;
}

/**
* Check if this block is a block entity, i.e. {@link #createBlockEntity(Vector3, CompoundTag)} should be invoked to
* create an entity from this block and its entity data tag. This is used for blocks that need to create a new entity
* that needs the block entity data, e.g. signs.
* <p>
* This is mutually exclusive with {@link #hasEntities()}, use that one if you don't need block entity data.
*
* @return True if this block is a block entity, false otherwise
*/
public boolean isBlockEntity() {
return false;
}

public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
throw new Error("This block type can not be converted to a block entity: "
/**
* Create a block entity from this block and the given block entity tag at the specified position.
*
* @param position Position
* @param entityTag Block entity tag
* @return The block entity created from this block's data and the entity tag
* @throws UnsupportedOperationException If this block is not a block entity (i.e. {@link #isBlockEntity()} returns false
*/
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An additional thought, is it possible for a user to need both the entity tag and the ability to return multiple entities?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closest we have is the decorated pot, which uses the block entity information to update the block tag (creating a new Block instance) and then uses an entity to create the upper part (which is higher than 1x1x1 and thus can't be in the octree).

throw new UnsupportedOperationException("This block type can not be converted to a block entity: "
+ getClass().getSimpleName());
}

public boolean isEntity() {
/**
* Check if this block has entities, i.e. {@link #createEntities(Vector3)} should be invoked to create entities from this
* block. A block can create multiple entities (e.g. the lectern may create a lectern and a book entity).
* <p>
* This is mutually exclusive with {@link #isBlockEntity()}, use that one if you need block entity data.
*
* @return True if this block has entities, false otherwise
*/
public boolean hasEntities() {
return false;
}

/**
* If this returns true, the block won't be removed from the octree even if this is an entity
* (i.e. {@link #isEntity()} returns true). This can be used for blocks that also contain
* entities, e.g. candle (where the candle flame is an entity).
* Create entities from this block at the specified position.
* <p>
* This may return multiple entities, e.g. the lectern has a lectern entity and an optional book entity.
*
* @param position Position
* @return The entities created from this block's data
* @throws UnsupportedOperationException If this block is not a block entity (i.e. {@link #hasEntities()} returns false
*/
public boolean isBlockWithEntity() {
return false;
public Collection<Entity> createEntities(Vector3 position) {
throw new UnsupportedOperationException("This block type can not be converted to entities: "
+ getClass().getSimpleName());
}

public Entity toEntity(Vector3 position) {
throw new Error("This block type can not be converted to an entity: "
+ getClass().getSimpleName());
/**
* Whether to remove this block from the octree if it contains entities (i.e. {@link #hasEntities()} or {@link #isBlockEntity()}
* return true).
* <p>
* Most blocks are replaced by their entities (eg. signs create a sign entity that does the rendering and the block itself
* does nothing, but some blocks use block model and entities, e.g. candle (where the candle flame is an entity but the candle is a block).
*/
public boolean isReplacedByEntities() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that isBlockEntity represented this behaviour already, you either isBlockEntity and get replaced with the result, or hasEntity and don't.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make any sense to just have this api hasEntity createEntities isReplacedByEntities, removing the second path?

Or have I just misunderstood the idea?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that isBlockEntity represented this behaviour already

It didn't do that yet (that's what this PR does) but it's indeed what I would have expected.

Does it make any sense to [remove toBlockEntity]?

Maybe. The only reason it exists is because we need block entity data for some entities but not for other entities where that block entity data isn't even available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just feels a bit weird that the callee decides which method gets called by two booleans, obviously not blocking ^>^

It would then have to be Collection<Entity> createEntities(Vector3 position, Optional<CompoundTag> entityTag)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I absolutely agree. The current interface let's the block decide what to implement bit the callee decides which method to call. That only works well because there is only one callee.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NotStirred The problem is that we don't have the tile entity data when we instantiate the block. We load a chunk's tile entities after all blocks have been loaded and then either create block entities or create a new tag (and from that, a new block) with the tile entity data.

Collection<Entity> createEntities(Vector3 position, Optional<CompoundTag> entityTag)

If we were to do this, we would need to load the tile entities first, put them into some Map<Position, CompoundTag> and then get the correct entity tag while iterating through the blocks. The interface wouldn't be good though. If the tile entity is there, it isn't optional – it is required to create the entities.

A better way to think about createEntities and createBlockEntity is probably to think about block instances as factories for the entities that correspond to their block type. And of course, the callee of a factory knows which method to call (and the factory has methods to tell the callee what it supports).

return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public class LegacyBanner extends MinecraftBlockTranslucent {
public LegacyBanner(String name, CompoundTag tag) {
super(name, Texture.whiteWool);
localIntersect = true;
invisible = true;
rotation = tag.get("Data").intValue(0);
}

Expand All @@ -56,15 +55,10 @@ public boolean isBlockEntity() {
}

@Override
public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
return new StandingBanner(position, rotation, parseDesign(entityTag));
}

@Override
public boolean intersect(Ray ray, Scene scene) {
return false;
}

/**
* Parse a banner design from the given Minecraft 1.12 or older banner entity tag and convert the
* colors to 1.13+ values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public LegacySkull(String name, CompoundTag tag) {
super(name, Texture.steve);
this.placement = tag.get("Data").intValue(0);
localIntersect = true;
invisible = true;
}

@Override
Expand All @@ -39,7 +38,7 @@ public boolean isBlockEntity() {
}

@Override
public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
Kind kind = getSkullKind(entityTag.get("SkullType").byteValue(0));
int rotation = entityTag.get("Rot").byteValue(0);
if (kind == Kind.PLAYER) {
Expand All @@ -55,11 +54,6 @@ public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
return new SkullEntity(position, kind, rotation, placement);
}

@Override
public boolean intersect(Ray ray, Scene scene) {
return false;
}

private static Kind getSkullKind(int skullType) {
switch (skullType) {
case 0:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public class LegacyWallBanner extends MinecraftBlockTranslucent {
public LegacyWallBanner(String name, CompoundTag tag) {
super(name, Texture.whiteWool);
localIntersect = true;
invisible = true;
facing = tag.get("Data").intValue(2);
}

Expand All @@ -32,12 +31,7 @@ public boolean isBlockEntity() {
}

@Override
public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
return new WallBanner(position, facing, LegacyBanner.parseDesign(entityTag));
}

@Override
public boolean intersect(Ray ray, Scene scene) {
return false;
}
}
1 change: 0 additions & 1 deletion chunky/src/java/se/llbit/chunky/block/minecraft/Air.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ private Air() {
super("air", Texture.air);
solid = false;
opaque = false;
invisible = true;
}
}
8 changes: 1 addition & 7 deletions chunky/src/java/se/llbit/chunky/block/minecraft/Banner.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,16 @@ public class Banner extends MinecraftBlockTranslucent {

public Banner(String name, Texture texture, int rotation, BannerDesign.Color color) {
super(name, texture);
invisible = true;
opaque = false;
localIntersect = true;
this.rotation = rotation % 16;
this.color = color;
}

@Override public boolean intersect(Ray ray, Scene scene) {
return false;
}

@Override public boolean isBlockEntity() {
return true;
}

@Override public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
@Override public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
JsonObject design = StandingBanner.parseDesign(entityTag);
design.set("base", Json.of(color.id)); // Base color is not included in the entity tag in Minecraft 1.13+.
return new StandingBanner(position, rotation, design);
Expand Down
6 changes: 3 additions & 3 deletions chunky/src/java/se/llbit/chunky/block/minecraft/Beacon.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public Beacon() {
}

@Override
public boolean isBlockWithEntity() {
return true;
public boolean isReplacedByEntities() {
return false;
}

@Override
Expand All @@ -46,7 +46,7 @@ public boolean isBlockEntity() {
}

@Override
public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
if (entityTag.get("Levels").intValue(0) > 0) {
return new BeaconBeam(position);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import se.llbit.chunky.resources.Texture;
import se.llbit.math.Vector3;

import java.util.Collection;
import java.util.Collections;
import java.util.Random;

public class CakeWithCandle extends AbstractModelBlock {
Expand All @@ -51,18 +53,18 @@ public String description() {
}

@Override
public boolean isEntity() {
public boolean hasEntities() {
return isLit();
}

@Override
public boolean isBlockWithEntity() {
return true;
public boolean isReplacedByEntities() {
return false;
}

@Override
public Entity toEntity(Vector3 position) {
return new FlameParticles(position, entity);
public Collection<Entity> createEntities(Vector3 position) {
return Collections.singleton(new FlameParticles(position, entity));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import se.llbit.chunky.resources.Texture;
import se.llbit.math.Vector3;

import java.util.Collection;
import java.util.Collections;

public class CalibratedSculkSensor extends AbstractModelBlock {
private final String phase;
private final String facing;
Expand All @@ -46,17 +49,17 @@ public String description() {
}

@Override
public boolean isEntity() {
public boolean hasEntities() {
return true;
}

@Override
public boolean isBlockWithEntity() {
return true;
public boolean isReplacedByEntities() {
return false;
}

@Override
public Entity toEntity(Vector3 position) {
return new CalibratedSculkSensorAmethyst(position, this.facing, isActive(), this);
public Collection<Entity> createEntities(Vector3 position) {
return Collections.singleton(new CalibratedSculkSensorAmethyst(position, this.facing, isActive(), this));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,19 @@ public class Campfire extends MinecraftBlockTranslucent {

public Campfire(String name, se.llbit.chunky.entity.Campfire.Kind kind, String facing, boolean lit) {
super(name, Texture.campfireLog);
invisible = true;
opaque = false;
localIntersect = true;
this.kind = kind;
this.facing = facing;
this.isLit = lit;
}

@Override
public boolean intersect(Ray ray, Scene scene) {
return false;
}

@Override
public boolean isBlockEntity() {
return true;
}

@Override
public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) {
public Entity createBlockEntity(Vector3 position, CompoundTag entityTag) {
return new se.llbit.chunky.entity.Campfire(this.kind, position, this.facing, this.isLit, this);
}

Expand Down
14 changes: 8 additions & 6 deletions chunky/src/java/se/llbit/chunky/block/minecraft/Candle.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import se.llbit.chunky.world.material.TextureMaterial;
import se.llbit.math.Vector3;

import java.util.Collection;
import java.util.Collections;
import java.util.Random;

public class Candle extends AbstractModelBlock {
Expand Down Expand Up @@ -79,21 +81,21 @@ public boolean isLit() {
}

@Override
public boolean isEntity() {
public boolean hasEntities() {
return isLit();
}

@Override
public boolean isBlockWithEntity() {
return true;
public boolean isReplacedByEntities() {
return false;
}

@Override
public Entity toEntity(Vector3 position) {
public Collection<Entity> createEntities(Vector3 position) {
if (entity != null) {
return new FlameParticles(position, entity);
return Collections.singleton(new FlameParticles(position, entity));
} else {
return new FlameParticles(position, this, new Vector3[0]);
return Collections.singleton(new FlameParticles(position, this, new Vector3[0]));
}
}

Expand Down
15 changes: 6 additions & 9 deletions chunky/src/java/se/llbit/chunky/block/minecraft/CoralFan.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
import se.llbit.math.Ray;
import se.llbit.math.Vector3;

import java.util.Collection;
import java.util.Collections;

public class CoralFan extends MinecraftBlockTranslucent {

private final String coralType;

public CoralFan(String name, String coralType) {
super(name, coralTexture(coralType));
this.coralType = coralType;
localIntersect = true;
solid = false;
invisible = true;
}

public static Texture coralTexture(String coralType) {
Expand Down Expand Up @@ -64,15 +65,11 @@ public static Texture coralTexture(String coralType) {
}
}

@Override public boolean intersect(Ray ray, Scene scene) {
return false;
}

@Override public boolean isEntity() {
@Override public boolean hasEntities() {
return true;
}

@Override public Entity toEntity(Vector3 position) {
return new CoralFanEntity(position, coralType);
@Override public Collection<Entity> createEntities(Vector3 position) {
return Collections.singleton(new CoralFanEntity(position, coralType));
}
}
Loading