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
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ dependencies {
compileOnly "mezz.jei:jei-${project.minecraft_version}-neoforge:${project.jei_version}"

compileOnly "curse.maven:jade-324717:${project.jade_version}"
compileOnly "curse.maven:lootr-361276:${project.lootr_version}"
// Lootr via Maven for full API access
compileOnly "noobanidus.mods.lootr:lootr-neoforge:${project.lootr_maven_version}"

// implementation fileTree(dir: 'libs', include: '*.jar')

Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ debugutils_version=1.0.6
jei_version=19.21.0.246
jade_version=5813144
lootr_version=5832064
lootr_maven_version=1.21.1-1.11.36.109
rei_version=16.0.783
cloth_config_version=15.0.140
architectury_version=13.0.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.neoforged.fml.ModList;
import net.neoforged.neoforge.event.EventHooks;

import java.util.function.Supplier;
Expand Down Expand Up @@ -84,7 +85,13 @@ public BlockEntityType<? extends TreasureChestBlockEntity> blockEntityType() {

@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
return level.isClientSide() ? createTickerHelper(blockEntityType, this.blockEntityType(), TreasureChestBlockEntity::lidAnimateTick) : null;
if (level.isClientSide()) {
return createTickerHelper(blockEntityType, this.blockEntityType(), TreasureChestBlockEntity::lidAnimateTick);
} else if (ModList.get().isLoaded("lootr")) {
// Lootr integration: use Lootr's ticker for decay/refresh mechanics
return com.aetherteam.aether.integration.lootr.LootrCompat.getTicker();
}
return null;
}

@Override
Expand All @@ -98,6 +105,7 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource
/**
* [CODE COPY] - {@link ChestBlock#useWithoutItem(BlockState, Level, BlockPos, Player, BlockHitResult)}.<br><br>
* Handles behavior for checking if a chest is locked and being able to unlock the chest.
* When Lootr is installed, uses Lootr's per-player inventory system.
*
* @param state The {@link BlockState} of the block.
* @param level The {@link Level} the block is in.
Expand All @@ -114,11 +122,23 @@ public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos
if (level.isClientSide()) {
return InteractionResult.SUCCESS;
} else {
MenuProvider menuprovider = this.getMenuProvider(state, level, pos);
if (menuprovider != null) {
player.openMenu(menuprovider);
// Lootr integration: use per-player inventory when Lootr is installed
if (ModList.get().isLoaded("lootr")) {
if (player.isShiftKeyDown()) {
com.aetherteam.aether.integration.lootr.LootrCompat.handleTreasureChestSneak(player, pos, level);
} else {
com.aetherteam.aether.integration.lootr.LootrCompat.openTreasureChest(player, pos, level);
}
player.awardStat(Stats.CUSTOM.get(Stats.OPEN_CHEST));
PiglinAi.angerNearbyPiglins(player, true);
} else {
// Default behavior without Lootr
MenuProvider menuprovider = this.getMenuProvider(state, level, pos);
if (menuprovider != null) {
player.openMenu(menuprovider);
player.awardStat(Stats.CUSTOM.get(Stats.OPEN_CHEST));
PiglinAi.angerNearbyPiglins(player, true);
}
}

return InteractionResult.CONSUME;
Expand Down Expand Up @@ -185,6 +205,15 @@ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState sta
}
}

@Override
public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, ItemStack itemStack) {
super.playerDestroy(level, player, pos, state, blockEntity, itemStack);
// Lootr integration: notify Lootr when chest is destroyed
if (ModList.get().isLoaded("lootr") && blockEntity instanceof TreasureChestBlockEntity treasureChest) {
com.aetherteam.aether.integration.lootr.LootrCompat.onTreasureChestDestroyed(level, player, pos, treasureChest);
}
}

/**
* Prevents Treasure Chests from being broken when they're locked, but allows it when they're unlocked.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import com.aetherteam.aether.block.AetherBlocks;
import com.aetherteam.aether.item.components.AetherDataComponents;
import com.aetherteam.aether.item.components.DungeonKind;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
Expand All @@ -31,6 +35,8 @@
import net.minecraft.world.level.block.entity.*;
import net.minecraft.world.level.block.state.BlockState;

import java.util.Set;
import java.util.UUID;
import java.util.stream.IntStream;

/**
Expand All @@ -39,6 +45,13 @@
*/
public class TreasureChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity, WorldlyContainer {
private NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);

// Lootr integration fields
private UUID lootrInfoId = null;
private boolean lootrHasBeenOpened = false;
private final Set<UUID> lootrClientOpeners = new ObjectOpenHashSet<>();
private boolean lootrClientOpened = false;

private final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
protected void onOpen(Level level, BlockPos pos, BlockState state) {
TreasureChestBlockEntity.playSound(level, pos, state, SoundEvents.CHEST_OPEN);
Expand Down Expand Up @@ -181,6 +194,41 @@ public boolean getLocked() {
return this.locked;
}

// Lootr integration methods
public UUID getLootrInfoUUID() {
if (this.lootrInfoId == null) {
this.lootrInfoId = UUID.randomUUID();
}
return this.lootrInfoId;
}

public boolean getLootrHasBeenOpened() {
return this.lootrHasBeenOpened;
}

public void setLootrHasBeenOpened(boolean opened) {
this.lootrHasBeenOpened = opened;
}

public Set<UUID> getLootrClientOpeners() {
return this.lootrClientOpeners;
}

public boolean isLootrClientOpened() {
return this.lootrClientOpened;
}

public void setLootrClientOpened(boolean opened) {
this.lootrClientOpened = opened;
}

public boolean hasClientOpened(UUID uuid) {
if (this.lootrClientOpened) {
return true;
}
return !this.lootrClientOpeners.isEmpty() && this.lootrClientOpeners.contains(uuid);
}

@Override
public void startOpen(Player player) {
if (!this.remove && !player.isSpectator()) {
Expand Down Expand Up @@ -275,6 +323,18 @@ public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
if (!this.trySaveLootTable(tag)) {
ContainerHelper.saveAllItems(tag, this.items, registries);
}
// Lootr integration
if (this.lootrInfoId != null) {
tag.putUUID("LootrInfoId", this.lootrInfoId);
}
tag.putBoolean("LootrHasBeenOpened", this.lootrHasBeenOpened);
if (this.level != null && this.level.isClientSide() && !this.lootrClientOpeners.isEmpty()) {
ListTag openersList = new ListTag();
for (UUID opener : this.lootrClientOpeners) {
openersList.add(NbtUtils.createUUID(opener));
}
tag.put("LootrOpeners", openersList);
}
}

@Override
Expand All @@ -286,6 +346,20 @@ public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
if (!this.tryLoadLootTable(tag)) {
ContainerHelper.loadAllItems(tag, this.items, registries);
}
// Lootr integration
if (tag.hasUUID("LootrInfoId")) {
this.lootrInfoId = tag.getUUID("LootrInfoId");
}
if (tag.contains("LootrHasBeenOpened", Tag.TAG_BYTE)) {
this.lootrHasBeenOpened = tag.getBoolean("LootrHasBeenOpened");
}
this.lootrClientOpeners.clear();
if (tag.contains("LootrOpeners", Tag.TAG_LIST)) {
ListTag openersList = tag.getList("LootrOpeners", Tag.TAG_INT_ARRAY);
for (Tag openerTag : openersList) {
this.lootrClientOpeners.add(NbtUtils.loadUUID(openerTag));
}
}
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/aetherteam/aether/client/AetherAtlases.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public class AetherAtlases {
public static Material TREASURE_CHEST_LEFT_MATERIAL;
public static Material TREASURE_CHEST_RIGHT_MATERIAL;

// Lootr integration textures
public static Material LOOTR_TREASURE_CHEST_OPENED_MATERIAL;
public static Material LOOTR_TREASURE_CHEST_UNOPENED_MATERIAL;

/**
* Need to register these static values here from {@link AetherClient#clientSetup(FMLClientSetupEvent)},
* otherwise they'll be loaded too early from static initialization in the field.
Expand All @@ -20,6 +24,10 @@ public static void registerTreasureChestAtlases() {
TREASURE_CHEST_MATERIAL = getChestMaterial("treasure_chest");
TREASURE_CHEST_LEFT_MATERIAL = getChestMaterial("treasure_chest_left");
TREASURE_CHEST_RIGHT_MATERIAL = getChestMaterial("treasure_chest_right");

// Lootr textures (used when Lootr is installed)
LOOTR_TREASURE_CHEST_OPENED_MATERIAL = getChestMaterial("lootr/treasure_chest_opened");
LOOTR_TREASURE_CHEST_UNOPENED_MATERIAL = getChestMaterial("lootr/treasure_chest_unopened");
}

public static void registerWoodTypeAtlases() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.aetherteam.aether.blockentity.TreasureChestBlockEntity;
import com.aetherteam.aether.client.AetherAtlases;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.ChestRenderer;
import net.minecraft.client.resources.model.Material;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.neoforged.fml.ModList;

public class TreasureChestRenderer extends ChestRenderer<TreasureChestBlockEntity> {
public TreasureChestRenderer(BlockEntityRendererProvider.Context context) {
Expand All @@ -14,6 +16,17 @@ public TreasureChestRenderer(BlockEntityRendererProvider.Context context) {

@Override
protected Material getMaterial(TreasureChestBlockEntity blockEntity, ChestType chestType) {
// Lootr integration: use opened/unopened textures based on player's state
if (ModList.get().isLoaded("lootr") && chestType == ChestType.SINGLE) {
if (Minecraft.getInstance().player != null
&& !blockEntity.getLocked()
&& blockEntity.hasClientOpened(Minecraft.getInstance().player.getUUID())) {
return AetherAtlases.LOOTR_TREASURE_CHEST_OPENED_MATERIAL;
} else if (!blockEntity.getLocked()) {
return AetherAtlases.LOOTR_TREASURE_CHEST_UNOPENED_MATERIAL;
}
}

return switch (chestType) {
case LEFT -> AetherAtlases.TREASURE_CHEST_LEFT_MATERIAL;
case RIGHT -> AetherAtlases.TREASURE_CHEST_RIGHT_MATERIAL;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.aetherteam.aether.integration.lootr;

import com.aetherteam.aether.blockentity.TreasureChestBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import noobanidus.mods.lootr.common.api.LootrAPI;
import noobanidus.mods.lootr.common.api.data.ILootrInfoProvider;
import noobanidus.mods.lootr.common.api.data.blockentity.ILootrBlockEntity;

/**
* Wrapper class for Lootr API calls.
* This class should only be loaded when Lootr is present to avoid ClassNotFoundException.
*/
public class LootrCompat {

/**
* Opens a treasure chest using Lootr's per-player inventory system.
*
* @param player The player opening the chest
* @param pos The position of the chest
* @param level The level containing the chest
*/
public static void openTreasureChest(Player player, BlockPos pos, Level level) {
if (player instanceof ServerPlayer serverPlayer) {
ILootrInfoProvider provider = ILootrInfoProvider.of(pos, level);
if (provider != null) {
LootrAPI.handleProviderOpen(provider, serverPlayer);
}
}
}

/**
* Handles shift-click on a treasure chest to mark it as unopened.
*
* @param player The player interacting with the chest
* @param pos The position of the chest
* @param level The level containing the chest
*/
public static void handleTreasureChestSneak(Player player, BlockPos pos, Level level) {
if (player instanceof ServerPlayer serverPlayer) {
ILootrInfoProvider provider = ILootrInfoProvider.of(pos, level);
if (provider != null) {
LootrAPI.handleProviderSneak(provider, serverPlayer);
}
}
}

/**
* Called when a treasure chest is destroyed to handle Lootr cleanup.
*
* @param level The level
* @param player The player who destroyed the chest
* @param pos The position of the chest
* @param blockEntity The block entity
*/
public static void onTreasureChestDestroyed(Level level, Player player, BlockPos pos, TreasureChestBlockEntity blockEntity) {
LootrAPI.playerDestroyed(level, player, pos, blockEntity);
}

/**
* Checks if a player has already opened a specific treasure chest (client-side).
*
* @param blockEntity The treasure chest block entity
* @param player The player to check
* @return true if the player has opened this chest
*/
public static boolean hasClientOpened(TreasureChestBlockEntity blockEntity, Player player) {
return blockEntity.hasClientOpened(player.getUUID());
}

/**
* @return true if Lootr is configured to use vanilla textures
*/
public static boolean isVanillaTextures() {
return LootrAPI.isVanillaTextures();
}

/**
* Gets the Lootr block entity ticker for decay/refresh mechanics.
*
* @return The Lootr ticker
*/
@SuppressWarnings("unchecked")
public static <T extends BlockEntity> BlockEntityTicker<T> getTicker() {
return (BlockEntityTicker<T>) (BlockEntityTicker<TreasureChestBlockEntity>) ILootrBlockEntity::ticker;
}
}
Loading