Skip to content

Commit

Permalink
Add tree decoration (#164)
Browse files Browse the repository at this point in the history
* Add tree decoration

* Allow players to remove ornaments

* Allow players to interact with ornaments to see additional details

* Restrict ornament placement positions that would intersect with a block

* Allow customizing the block tag that ornaments can be placed on
  • Loading branch information
haykam821 authored Dec 24, 2023
1 parent 7ff4bdb commit 446c369
Show file tree
Hide file tree
Showing 11 changed files with 546 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/main/java/xyz/nucleoid/extras/NucleoidExtras.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public void onInitialize() {
NEItems.register();
NEEntities.register();
NECriteria.register();
NEPointOfInterestTypes.register();

ChatFilter.register();
CommandAliases.register();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/xyz/nucleoid/extras/lobby/NEBlocks.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class NEBlocks {
public static final Block IRON_LAUNCH_PAD = new LaunchPadBlock(AbstractBlock.Settings.copy(Blocks.HEAVY_WEIGHTED_PRESSURE_PLATE).strength(100).noCollision(), Blocks.HEAVY_WEIGHTED_PRESSURE_PLATE);

public static final Block CONTRIBUTOR_STATUE = new ContributorStatueBlock(AbstractBlock.Settings.copy(Blocks.SMOOTH_STONE).strength(100));
public static final Block TREE_DECORATION = new TreeDecorationBlock(AbstractBlock.Settings.copy(Blocks.COARSE_DIRT).strength(100));

public static final Block INFINITE_DISPENSER = new InfiniteDispenserBlock(AbstractBlock.Settings.copy(Blocks.DISPENSER).strength(100));
public static final Block INFINITE_DROPPER = new InfiniteDropperBlock(AbstractBlock.Settings.copy(Blocks.DROPPER).strength(100));
Expand Down Expand Up @@ -428,6 +429,7 @@ public class NEBlocks {

public static final BlockEntityType<LaunchPadBlockEntity> LAUNCH_PAD_ENTITY = FabricBlockEntityTypeBuilder.create(LaunchPadBlockEntity::new, GOLD_LAUNCH_PAD, IRON_LAUNCH_PAD).build();
public static final BlockEntityType<ContributorStatueBlockEntity> CONTRIBUTOR_STATUE_ENTITY = FabricBlockEntityTypeBuilder.create(ContributorStatueBlockEntity::new, CONTRIBUTOR_STATUE).build();
public static final BlockEntityType<TreeDecorationBlockEntity> TREE_DECORATION_ENTITY = FabricBlockEntityTypeBuilder.create(TreeDecorationBlockEntity::new, TREE_DECORATION).build();
public static final BlockEntityType<TateroidBlockEntity> TATEROID_ENTITY = FabricBlockEntityTypeBuilder.create(TateroidBlockEntity::new, TATEROID, RED_TATEROID, ORANGE_TATEROID, YELLOW_TATEROID, GREEN_TATEROID, BLUE_TATEROID, PURPLE_TATEROID).build();
public static final BlockEntityType<DaylightDetectorTaterBlockEntity> DAYLIGHT_DETECTOR_TATER_ENTITY = FabricBlockEntityTypeBuilder.create(DaylightDetectorTaterBlockEntity::new, DAYLIGHT_DETECTOR_TATER, INVERTED_DAYLIGHT_DETECTOR_TATER).build();
public static final BlockEntityType<BellTaterBlockEntity> BELL_TATER_ENTITY = FabricBlockEntityTypeBuilder.create(BellTaterBlockEntity::new, BELL_TATER).build();
Expand Down Expand Up @@ -526,6 +528,7 @@ public static void register() {
register("gold_launch_pad", GOLD_LAUNCH_PAD);
register("iron_launch_pad", IRON_LAUNCH_PAD);
register("contributor_statue", CONTRIBUTOR_STATUE);
register("tree_decoration", TREE_DECORATION);
register("infinite_dispenser", INFINITE_DISPENSER);
register("infinite_dropper", INFINITE_DROPPER);
register("snake_block", SNAKE_BLOCK);
Expand Down Expand Up @@ -859,6 +862,7 @@ public static void register() {

registerBlockEntity("launch_pad", LAUNCH_PAD_ENTITY);
registerBlockEntity("contributor_statue", CONTRIBUTOR_STATUE_ENTITY);
registerBlockEntity("tree_decoration", TREE_DECORATION_ENTITY);
registerBlockEntity("tateroid", TATEROID_ENTITY);
registerBlockEntity("daylight_detector_tater", DAYLIGHT_DETECTOR_TATER_ENTITY);
registerBlockEntity("bell_tater", BELL_TATER_ENTITY);
Expand Down
24 changes: 20 additions & 4 deletions src/main/java/xyz/nucleoid/extras/lobby/NEItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,23 @@
import net.minecraft.item.Items;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import xyz.nucleoid.extras.NucleoidExtras;
import xyz.nucleoid.extras.NucleoidExtrasConfig;
import xyz.nucleoid.extras.lobby.block.TreeDecorationBlockEntity;
import xyz.nucleoid.extras.lobby.block.tater.TinyPotatoBlock;
import xyz.nucleoid.extras.lobby.item.*;
import xyz.nucleoid.extras.lobby.item.tater.CreativeTaterBoxItem;
Expand All @@ -53,6 +58,7 @@ public class NEItems {
entries.add(NEItems.GOLD_LAUNCH_PAD);
entries.add(NEItems.IRON_LAUNCH_PAD);
entries.add(NEItems.CONTRIBUTOR_STATUE);
entries.add(NEItems.TREE_DECORATION);
entries.add(NEItems.INFINITE_DISPENSER);
entries.add(NEItems.INFINITE_DROPPER);
entries.add(NEItems.SNAKE_BLOCK);
Expand Down Expand Up @@ -103,6 +109,7 @@ public class NEItems {
public static final Item IRON_LAUNCH_PAD = createSimple(NEBlocks.IRON_LAUNCH_PAD, Items.HEAVY_WEIGHTED_PRESSURE_PLATE);

public static final Item CONTRIBUTOR_STATUE = createSimple(NEBlocks.CONTRIBUTOR_STATUE, Items.SMOOTH_STONE);
public static final Item TREE_DECORATION = createSimple(NEBlocks.TREE_DECORATION, Items.COARSE_DIRT);

public static final Item INFINITE_DISPENSER = createSimple(NEBlocks.INFINITE_DISPENSER, Items.DISPENSER);
public static final Item INFINITE_DROPPER = createSimple(NEBlocks.INFINITE_DROPPER, Items.DROPPER);
Expand Down Expand Up @@ -462,6 +469,7 @@ public static void register() {
register("gold_launch_pad", GOLD_LAUNCH_PAD);
register("iron_launch_pad", IRON_LAUNCH_PAD);
register("contributor_statue", CONTRIBUTOR_STATUE);
register("tree_decoration", TREE_DECORATION);
register("infinite_dispenser", INFINITE_DISPENSER);
register("infinite_dropper", INFINITE_DROPPER);
register("snake_block", SNAKE_BLOCK);
Expand Down Expand Up @@ -843,11 +851,19 @@ private static void onPlayerJoin(ServerPlayNetworkHandler handler, PacketSender

private static ActionResult onUseBlock(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) {
if (!player.getWorld().isClient() && hitResult != null && hand == Hand.MAIN_HAND) {
ItemStack stack = player.getStackInHand(hand);
BlockPos pos = hitResult.getBlockPos();
var stack = player.getStackInHand(hand);
var pos = hitResult.getBlockPos();

PlayerLobbyState state = PlayerLobbyState.get(player);
state.collectTaterFromBlock(world, pos, stack, player);
var lobbyState = PlayerLobbyState.get(player);

if (lobbyState.collectTaterFromBlock(world, pos, stack, player) == ActionResult.PASS && !(stack.getItem() instanceof TaterBoxItem)) {
var serverWorld = (ServerWorld) world;
var blockEntity = TreeDecorationBlockEntity.findNearestTreeDecoration(serverWorld, pos);

if (blockEntity.isPresent() && blockEntity.get().placeOrnament((ServerPlayerEntity) player, serverWorld, hand, hitResult)) {
return ActionResult.SUCCESS;
}
}
}

return ActionResult.PASS;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package xyz.nucleoid.extras.lobby;

import net.fabricmc.fabric.api.object.builder.v1.world.poi.PointOfInterestHelper;
import net.minecraft.block.Block;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.world.poi.PointOfInterestType;
import xyz.nucleoid.extras.NucleoidExtras;

public class NEPointOfInterestTypes {
public static final RegistryKey<PointOfInterestType> TREE_DECORATION = of("tree_decoration");

public static void register() {
register(TREE_DECORATION, 0, 1, NEBlocks.TREE_DECORATION);
}

private static RegistryKey<PointOfInterestType> of(String id) {
return RegistryKey.of(RegistryKeys.POINT_OF_INTEREST_TYPE, NucleoidExtras.identifier(id));
}

private static PointOfInterestType register(RegistryKey<PointOfInterestType> key, int ticketCount, int searchDistance, Block... blocks) {
return PointOfInterestHelper.register(key.getValue(), ticketCount, searchDistance, blocks);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package xyz.nucleoid.extras.lobby.block;

import eu.pb4.polymer.core.api.block.PolymerBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.BlockWithEntity;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import xyz.nucleoid.extras.lobby.NEBlocks;

public class TreeDecorationBlock extends BlockWithEntity implements PolymerBlock {
public TreeDecorationBlock(Settings settings) {
super(settings);
}

@Override
public Block getPolymerBlock(BlockState state) {
return Blocks.COARSE_DIRT;
}

@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new TreeDecorationBlockEntity(pos, state);
}

@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return world.isClient() ? null : BlockWithEntity.checkType(type, NEBlocks.TREE_DECORATION_ENTITY, TreeDecorationBlockEntity::tick);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package xyz.nucleoid.extras.lobby.block;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.base.Predicates;

import eu.pb4.polymer.virtualentity.api.ElementHolder;
import eu.pb4.polymer.virtualentity.api.attachment.BlockBoundAttachment;
import eu.pb4.polymer.virtualentity.api.attachment.HolderAttachment;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import net.minecraft.world.poi.PointOfInterestStorage;
import xyz.nucleoid.extras.lobby.NEBlocks;
import xyz.nucleoid.extras.lobby.NEPointOfInterestTypes;
import xyz.nucleoid.extras.lobby.item.tater.TaterBoxItem;
import xyz.nucleoid.extras.lobby.tree.Ornament;
import xyz.nucleoid.extras.lobby.tree.OrnamentModel;
import xyz.nucleoid.extras.lobby.tree.TreeDecoration;

public class TreeDecorationBlockEntity extends BlockEntity {
private static final Logger LOGGER = LogManager.getLogger(TreeDecorationBlockEntity.class);

private static final String DECORATION_KEY = "decoration";

private static final int SEARCH_RADIUS = 32;

private TreeDecoration data = TreeDecoration.createEmpty();
private final Map<Ornament, OrnamentModel> ornamentsToModels = new HashMap<>();

private final ElementHolder holder = new ElementHolder();
private HolderAttachment attachment;

public TreeDecorationBlockEntity(BlockPos pos, BlockState state) {
super(NEBlocks.TREE_DECORATION_ENTITY, pos, state);
}

private void setData(TreeDecoration data) {
this.data = data;

// Add models that have been added to the data
for (var ornament : data.getOrnaments()) {
if (!this.ornamentsToModels.containsKey(ornament)) {
var model = new OrnamentModel(this, ornament);

model.addToHolder(this.holder);
this.ornamentsToModels.put(ornament, model);
}
}

// Remove models that are no longer in the data
this.ornamentsToModels.entrySet().removeIf(entry -> {
if (!data.getOrnaments().contains(entry.getKey())) {
entry.getValue().removeFromHolder(this.holder);
return true;
}

return false;
});
}

private void addOrnament(Ornament ornament) {
this.setData(this.data.withOrnament(ornament));
this.markDirty();
}

public void removeOrnament(Ornament ornament) {
this.setData(this.data.exceptOrnament(ornament));
this.markDirty();

var pos = this.pos.toCenterPos().add(ornament.offset());

float pitch = 1.3f + world.getRandom().nextFloat() * 0.2f;
world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), SoundEvents.BLOCK_CHAIN_BREAK, SoundCategory.BLOCKS, 0.5f, pitch);
}

public boolean placeOrnament(ServerPlayerEntity player, ServerWorld world, Hand hand, BlockHitResult hitResult) {
var item = TaterBoxItem.getPrimaryCollectedTater(player).asItem();
if (item == Items.AIR) return false;

var blockPos = hitResult.getBlockPos();

var state = world.getBlockState(blockPos);
if (!state.isIn(this.data.getSupportedBlocks())) return false;

var pos = hitResult.getPos();
var side = hitResult.getSide();

if (side == Direction.UP) {
return false;
} else if (side != Direction.DOWN) {
var belowPos = blockPos.add(side.getOffsetX(), side.getOffsetY() - 1, side.getOffsetZ());

if (world.getBlockState(belowPos).isFullCube(world, belowPos)) {
double minY = blockPos.getY() + OrnamentModel.HEIGHT;
pos = pos.withAxis(Direction.Axis.Y, Math.max(minY, pos.getY()));
}
}

var offset = pos.subtract(this.pos.toCenterPos());

float yaw = player.getYaw() - 180;
float hookYaw = MathHelper.wrapDegrees(player.getRandom().nextFloat() * 360);

this.addOrnament(new Ornament(item, offset, yaw, hookYaw, player.getUuid()));

world.emitGameEvent(player, GameEvent.ENTITY_PLACE, pos);

float pitch = 1.3f + player.getRandom().nextFloat() * 0.2f;
world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), SoundEvents.BLOCK_CHAIN_PLACE, SoundCategory.BLOCKS, 0.5f, pitch);

return true;
}

@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);

if (nbt.contains(DECORATION_KEY, NbtElement.COMPOUND_TYPE)) {
TreeDecoration.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound(DECORATION_KEY))
.resultOrPartial(LOGGER::error)
.ifPresent(this::setData);
}
}

@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);

TreeDecoration.CODEC.encodeStart(NbtOps.INSTANCE, this.data)
.resultOrPartial(LOGGER::error)
.ifPresent(element -> {
nbt.put(DECORATION_KEY, element);
});
}

@Override
public void markRemoved() {
super.markRemoved();
this.holder.destroy();
}

public static void tick(World world, BlockPos pos, BlockState state, TreeDecorationBlockEntity blockEntity) {
for (var model : blockEntity.ornamentsToModels.values()) {
model.tick();
}

if (blockEntity.attachment == null && world instanceof ServerWorld serverWorld) {
blockEntity.attachment = BlockBoundAttachment.ofTicking(blockEntity.holder, serverWorld, pos);
}
}

public static Optional<TreeDecorationBlockEntity> findNearestTreeDecoration(ServerWorld world, BlockPos pos) {
return world.getPointOfInterestStorage()
.getNearestPosition(poiType -> {
return poiType.matchesKey(NEPointOfInterestTypes.TREE_DECORATION);
}, Predicates.alwaysTrue(), pos, SEARCH_RADIUS, PointOfInterestStorage.OccupationStatus.ANY)
.flatMap(decorationPos -> {
return world.getBlockEntity(decorationPos, NEBlocks.TREE_DECORATION_ENTITY);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.extras.NucleoidExtras;
import xyz.nucleoid.extras.lobby.NEBlocks;
import xyz.nucleoid.extras.lobby.PlayerLobbyState;
import xyz.nucleoid.extras.lobby.block.tater.CubicPotatoBlock;
import xyz.nucleoid.extras.lobby.block.tater.TinyPotatoBlock;
Expand Down Expand Up @@ -234,4 +235,28 @@ public static void setSelectedTater(ItemStack stack, @Nullable Identifier select
tag.putString(SELECTED_TATER_KEY, selectedTaterId.toString());
}
}

public static Block getPrimaryCollectedTater(ServerPlayerEntity player) {
var inventory = player.getInventory();

// Check any tater boxes in the inventory for a selected tater
for (int slot = 0; slot < inventory.size(); slot++) {
var stack = inventory.getStack(slot);

if (stack.getItem() instanceof TaterBoxItem) {
var selectedTater = getSelectedTater(stack);
if (selectedTater != null) return selectedTater;
}
}

var collectedTaters = new ArrayList<>(PlayerLobbyState.get(player).collectedTaters);

if (collectedTaters.isEmpty()) {
// If no taters collected at all, fall back to tiny potato
return NEBlocks.TINY_POTATO;
} else {
// Use a random tater that has already been collected
return Util.getRandom(collectedTaters, player.getRandom());
}
}
}
Loading

0 comments on commit 446c369

Please sign in to comment.