Allow any book to be placed in a bookshelf

- Modifies the bookshelf model and blockstate to choose a texture dynamically based on the book item
- Changes bookshelf slots to accept all books from wizardry and vanilla minecraft by default
- Adds a couple of hooks to allow addons to register their own book items and model textures
- Adds a config option to allow modpack makers to add more book items
This commit is contained in:
Electroblob77
2020-05-15 22:52:32 +01:00
parent 28da6810d7
commit 48a350b63a
15 changed files with 186 additions and 41 deletions
@@ -296,6 +296,8 @@ public final class Settings {
Wizardry.MODID + ":acacia_bookshelf",
Wizardry.MODID + ":dark_oak_bookshelf"
);
/** <b>[Synchronised]</b> List of registry names of items that can be placed in a bookshelf. */
public Pair<ResourceLocation, Short>[] bookItems = parseItemMetaStrings();
// Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys
// its own values for these, and changing them on a dedicated server will have no effect.
@@ -735,6 +737,11 @@ public final class Settings {
bookshelfBlocks = parseItemMetaStrings(property.getStringList());
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "bookItems", new String[0], "List of registry names of items which can be placed in a bookshelf, in addition to the defaults. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. thaumcraft:thaumonomicon).");
property.setLanguageKey("config." + Wizardry.MODID + ".book_items");
bookItems = parseItemMetaStrings(property.getStringList());
propOrder.add(property.getName());
property = config.get(TWEAKS_CATEGORY, "bookshelfSearchRadius", 4,
"The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it.",
1, 10);
@@ -1,5 +1,6 @@
package electroblob.wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.command.CommandCastSpell;
import electroblob.wizardry.command.CommandDiscoverSpell;
import electroblob.wizardry.command.CommandSetAlly;
@@ -8,6 +9,7 @@ import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
import electroblob.wizardry.inventory.ContainerBookshelf;
import electroblob.wizardry.misc.Forfeit;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.*;
@@ -123,6 +125,7 @@ public class Wizardry {
WizardryLoot.register();
WizardryAdvancementTriggers.register();
Forfeit.register();
BlockBookshelf.registerStandardBookModelTextures();
// Client-side stuff (via proxies)
proxy.registerRenderers();
@@ -159,6 +162,8 @@ public class Wizardry {
WizardryPacketHandler.initPackets();
// Post-registry extras
BlockBookshelf.compileBookModelTextures();
ContainerBookshelf.initDefaultBookItems();
WizardryItems.populateWandMap();
WizardryItems.populateArmourMap();
WizardryItems.registerDispenseBehaviours();
@@ -1,28 +1,29 @@
package electroblob.wizardry.block;
import com.google.common.collect.ImmutableList;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.tileentity.TileEntityBookshelf;
import net.minecraft.block.BlockHorizontal;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.IWorldEventListener;
@@ -31,12 +32,17 @@ import net.minecraftforge.common.property.IExtendedBlockState;
import net.minecraftforge.common.property.Properties;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@Mod.EventBusSubscriber
public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvider {
@@ -46,13 +52,11 @@ public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvid
public static final double PLAYER_NOTIFY_RANGE = 32;
public static final int SLOT_COUNT = 12;
public static final UnlistedPropertyBool[] BOOKS = new UnlistedPropertyBool[SLOT_COUNT];
public static final UnlistedPropertyInt[] BOOKS = new UnlistedPropertyInt[SLOT_COUNT];
static {
for(int i=0; i<SLOT_COUNT; i++){
BOOKS[i] = new UnlistedPropertyBool("book" + i);
}
}
private static final Map<Supplier<Item>, ResourceLocation> BOOK_TEXTURE_MAP = new HashMap<>();
private static ImmutableList<Item> bookItems;
private static ImmutableList<ResourceLocation> bookTextures;
public BlockBookshelf(){
super(Material.WOOD);
@@ -115,9 +119,19 @@ public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvid
IExtendedBlockState s = (IExtendedBlockState)super.getExtendedState(state, world, pos);
if(world.getTileEntity(pos) instanceof TileEntityBookshelf){
TileEntityBookshelf tileentity = ((TileEntityBookshelf)world.getTileEntity(pos));
for(int i = 0; i < tileentity.getSizeInventory(); i++){
s = s.withProperty(BOOKS[i], !tileentity.getStackInSlot(i).isEmpty());
if(tileentity.getStackInSlot(i).isEmpty()){
s = s.withProperty(BOOKS[i], bookItems.size());
}else{
Item item = tileentity.getStackInSlot(i).getItem();
// Default to the standard spell book texture, which will always be the first item in the list
s = s.withProperty(BOOKS[i], bookItems.contains(item) ? bookItems.indexOf(item) : 0);
}
}
}
@@ -176,14 +190,71 @@ public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvid
return tileentity != null && tileentity.receiveClientEvent(id, param);
}
// Copied from BlockFluidBase, only reason it exists is because of java's weird restrictions on generics
private static final class UnlistedPropertyBool extends Properties.PropertyAdapter<Boolean> {
/** Returns the list of book textures, in ID order. */
public static ImmutableList<ResourceLocation> getBookTextures(){
// ModelBookshelf calls this before init() so we need to return the map values as a fallback
return bookTextures == null ? ImmutableList.copyOf(BOOK_TEXTURE_MAP.values()) : bookTextures;
}
public UnlistedPropertyBool(String name){
super(PropertyBool.create(name));
/** Called during block registration to initialise the block properties for each book. */
public static void initBookProperties(){
for(int i=0; i<SLOT_COUNT; i++){
BOOKS[i] = new UnlistedPropertyInt("book" + i);
}
}
// Based on BlockFluid's UnlistedPropertyBool; this is only needed because of Java's weird restrictions on generics
private static final class UnlistedPropertyInt extends Properties.PropertyAdapter<Integer> {
public UnlistedPropertyInt(String name){
// Max is inclusive here, but we're using the largest value for no book so there's an extra one (can't use -1)
super(PropertyInteger.create(name, 0, BOOK_TEXTURE_MAP.size()));
}
}
/** Called from {@link Wizardry#init(FMLInitializationEvent)} to retrieve the actual book items and compile them
* and their textures into immutable lists. */
public static void compileBookModelTextures(){
ImmutableList.Builder<Item> itemListBuider = ImmutableList.builder();
ImmutableList.Builder<ResourceLocation> textureListBuilder = ImmutableList.builder();
for(Map.Entry<Supplier<Item>, ResourceLocation> entry : BOOK_TEXTURE_MAP.entrySet()){
itemListBuider.add(entry.getKey().get());
textureListBuilder.add(entry.getValue());
}
bookItems = itemListBuider.build();
bookTextures = textureListBuilder.build();
}
/** Called from {@link Wizardry#preInit(FMLPreInitializationEvent)} to register the default set of book textures. */
public static void registerStandardBookModelTextures(){
// Vanilla Minecraft books
// Regular brown books are the default so they're registered first
registerBookModelTexture(() -> Items.BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
registerBookModelTexture(() -> Items.WRITABLE_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
registerBookModelTexture(() -> Items.WRITTEN_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown"));
registerBookModelTexture(() -> Items.ENCHANTED_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_enchanted"));
// Wizardry books
registerBookModelTexture(() -> WizardryItems.spell_book, new ResourceLocation(Wizardry.MODID, "blocks/books_red"));
registerBookModelTexture(() -> WizardryItems.wizard_handbook, new ResourceLocation(Wizardry.MODID, "blocks/books_blue"));
registerBookModelTexture(() -> WizardryItems.arcane_tome, new ResourceLocation(Wizardry.MODID, "blocks/books_purple"));
}
/**
* Registers a book texture to be used for the book model when the given item is placed in a bookshelf. This method
* <b>must</b> be called from {@code preInit} as the registered item count is required during block registration.
* This also necessitates the use of a supplier to fetch the item, since the items will not yet be registered.
* @param itemFactory A {@link Supplier} that returns the book item to link the texture to. This item need not
* be an instance of {@link electroblob.wizardry.item.ItemSpellBook ItemSpellBook}. Duplicate
* items will result in an {@link IllegalArgumentException} being thrown later.
* @param texture The texture to apply to the book model.
*/
public static void registerBookModelTexture(Supplier<Item> itemFactory, ResourceLocation texture){
BOOK_TEXTURE_MAP.put(itemFactory, texture);
}
/**
* Returns a list of nearby bookshelves' inventories, where 'bookshelves' are any tile entities with inventories
* whose blocks are specified in the config file under the {@code bookshelfBlocks} option.
@@ -17,9 +17,9 @@ import java.util.List;
public class BakedModelBookshelf implements IBakedModel {
private final IBakedModel bookshelf;
private final IBakedModel[] books;
private final IBakedModel[][] books;
public BakedModelBookshelf(IBakedModel bookshelf, IBakedModel... books){
public BakedModelBookshelf(IBakedModel bookshelf, IBakedModel[][] books){
this.bookshelf = bookshelf;
this.books = books;
}
@@ -36,9 +36,9 @@ public class BakedModelBookshelf implements IBakedModel {
IExtendedBlockState extendedState = (IExtendedBlockState)state;
for(int i = 0; i<BlockBookshelf.SLOT_COUNT; i++){
Boolean value = extendedState.getValue(BlockBookshelf.BOOKS[i]);
if(value == null) return fallback.getQuads(null, side, rand);
if(value) quads.addAll(books[i].getQuads(state, side, rand));
Integer value = extendedState.getValue(BlockBookshelf.BOOKS[i]);
if(value == null || value < 0) return fallback.getQuads(null, side, rand);
if(value < books.length) quads.addAll(books[value][i].getQuads(state, side, rand)); // Empty slots use books.length
}
return quads;
@@ -1,6 +1,7 @@
package electroblob.wizardry.client.model;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import net.minecraft.client.renderer.block.model.IBakedModel;
@@ -19,7 +20,7 @@ import java.util.function.Function;
public class ModelBookshelf implements IModel {
private static final ResourceLocation BOOK_TEXTURE = new ResourceLocation(Wizardry.MODID, "blocks/books");
private static final ResourceLocation DEFAULT_BOOK_TEXTURE = new ResourceLocation(Wizardry.MODID, "blocks/books");
private static final List<ResourceLocation> bookModelLocations = new ArrayList<>();
@@ -48,11 +49,18 @@ public class ModelBookshelf implements IModel {
// I don't know why default state works here (surely it ought to be the state param?), but it works so who cares
IBakedModel bookshelf = bookshelfModel.bake(bookshelfModel.getDefaultState(), format, bakedTextureGetter);
IBakedModel[] books = new IBakedModel[BlockBookshelf.SLOT_COUNT];
ImmutableList<ResourceLocation> textures = BlockBookshelf.getBookTextures();
for(int i = 0; i < BlockBookshelf.SLOT_COUNT; i++){
IModel bookModel = ModelLoaderRegistry.getModel(new ModelResourceLocation(bookModelLocations.get(i), variant));
books[i] = bookModel.bake(bookModel.getDefaultState(), format, bakedTextureGetter); // Same here!
IBakedModel[][] books = new IBakedModel[textures.size()][BlockBookshelf.SLOT_COUNT];
for(int i = 0; i < textures.size(); i++){
ImmutableMap<String, String> retexturer = ImmutableMap.of("books", textures.get(i).toString());
for(int j = 0; j < BlockBookshelf.SLOT_COUNT; j++){
IModel bookModel = ModelLoaderRegistry.getModel(new ModelResourceLocation(bookModelLocations.get(j), variant)).retexture(retexturer);
books[i][j] = bookModel.bake(bookModel.getDefaultState(), format, bakedTextureGetter); // Same here!
}
}
// To clarify: the whole point of doing this was to avoid having to bake 4 * 2^12 models per bookshelf type
@@ -85,7 +93,7 @@ public class ModelBookshelf implements IModel {
// 3. Is it correct to use getModel() to get the dependency models upon construction of this class so I can
// access their textures properly? Or is that too early?
// 4. If so, what's the point of getDependencies, and why should I override it?
return ImmutableList.of(BOOK_TEXTURE);
return BlockBookshelf.getBookTextures();// ImmutableList.of(DEFAULT_BOOK_TEXTURE);
}
}
@@ -1,17 +1,29 @@
package electroblob.wizardry.inventory;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockBookshelf;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityBookshelf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
public class ContainerBookshelf extends Container {
private static final Set<Item> validItems = new HashSet<>();
/** The bookshelf tile entity associated with this container. */
public TileEntityBookshelf tileentity;
@@ -21,7 +33,7 @@ public class ContainerBookshelf extends Container {
for(int y = 0; y < 2; y++){
for(int x = 0; x < BlockBookshelf.SLOT_COUNT / 2; x++){
this.addSlotToContainer(new SlotBookshelf(tileentity, x + BlockBookshelf.SLOT_COUNT / 2 * y, 35 + x * 18, 17 + y * 18, 64, ItemSpellBook.class));
this.addSlotToContainer(new SlotBookshelf(tileentity, x + BlockBookshelf.SLOT_COUNT / 2 * y, 35 + x * 18, 17 + y * 18));
}
}
@@ -94,11 +106,31 @@ public class ContainerBookshelf extends Container {
return remainder;
}
public class SlotBookshelf extends SlotItemClassList {
/**
* Adds the given item to the set of items that can be put in a bookshelf. This method should be called from the
* {@code init()} phase.
* @param item The item to register
* @see BlockBookshelf#registerBookModelTexture(Supplier, ResourceLocation)
*/
public static void registerBookItem(Item item){
validItems.add(item);
}
@SafeVarargs
public SlotBookshelf(IInventory inventory, int index, int x, int y, int stackLimit, Class<? extends Item>... allowedItemClasses){
super(inventory, index, x, y, stackLimit, allowedItemClasses);
/** Called from {@link Wizardry#init(FMLInitializationEvent)} to register the default book items. */
public static void initDefaultBookItems(){
registerBookItem(WizardryItems.spell_book);
registerBookItem(WizardryItems.arcane_tome);
registerBookItem(WizardryItems.wizard_handbook);
registerBookItem(Items.BOOK);
registerBookItem(Items.WRITTEN_BOOK);
registerBookItem(Items.WRITABLE_BOOK);
registerBookItem(Items.ENCHANTED_BOOK);
}
public class SlotBookshelf extends Slot {
public SlotBookshelf(IInventory inventory, int index, int x, int y){
super(inventory, index, x, y);
}
@Override
@@ -115,6 +147,10 @@ public class ContainerBookshelf extends Container {
return result;
}
@Override
public boolean isItemValid(ItemStack stack){
return validItems.contains(stack.getItem()) || Settings.containsMetaItem(Wizardry.settings.bookItems, stack);
}
}
}
@@ -41,6 +41,7 @@ public class PacketSyncSettings implements IMessageHandler<Message, IMessage> {
Wizardry.settings.forfeitChance = message.settings.forfeitChance;
Wizardry.settings.bookshelfSearchRadius = message.settings.bookshelfSearchRadius;
Wizardry.settings.bookshelfBlocks = message.settings.bookshelfBlocks;
Wizardry.settings.bookItems = message.settings.bookItems;
}
public static class Message implements IMessage {
@@ -57,7 +58,6 @@ public class PacketSyncSettings implements IMessageHandler<Message, IMessage> {
}
@Override
@SuppressWarnings("unchecked")
public void fromBytes(ByteBuf buf){
// I'm guessing the settings field will be null here, so it needs initialising.
// This is also a great reason to have the settings as an actual object.
@@ -69,12 +69,8 @@ public class PacketSyncSettings implements IMessageHandler<Message, IMessage> {
settings.replaceVanillaFireballs = buf.readBoolean();
settings.forfeitChance = buf.readFloat();
settings.bookshelfSearchRadius = buf.readInt();
int length = buf.readInt();
List<Pair<ResourceLocation, Short>> entries = new ArrayList<>();
for(int i=0; i<length; i++){
entries.add(Pair.of(new ResourceLocation(ByteBufUtils.readUTF8String(buf)), buf.readShort()));
}
settings.bookshelfBlocks = entries.toArray(new Pair[0]);
settings.bookshelfBlocks = readMetaItems(buf);
settings.bookItems = readMetaItems(buf);
}
@Override
@@ -85,11 +81,27 @@ public class PacketSyncSettings implements IMessageHandler<Message, IMessage> {
buf.writeBoolean(settings.replaceVanillaFireballs);
buf.writeFloat((float)settings.forfeitChance); // Configs don't have floats but this can only be 0-1 anyway
buf.writeInt(settings.bookshelfSearchRadius);
buf.writeInt(settings.bookshelfBlocks.length);
for(Pair<ResourceLocation, Short> entry : settings.bookshelfBlocks){
writeMetaItems(buf, settings.bookshelfBlocks);
writeMetaItems(buf, settings.bookItems);
}
@SuppressWarnings("unchecked")
private static Pair<ResourceLocation, Short>[] readMetaItems(ByteBuf buf){
int length = buf.readInt();
List<Pair<ResourceLocation, Short>> entries = new ArrayList<>();
for(int i=0; i<length; i++){
entries.add(Pair.of(new ResourceLocation(ByteBufUtils.readUTF8String(buf)), buf.readShort()));
}
return entries.toArray(new Pair[0]);
}
private static void writeMetaItems(ByteBuf buf, Pair<ResourceLocation, Short>[] items){
buf.writeInt(items.length);
for(Pair<ResourceLocation, Short> entry : items){
ByteBufUtils.writeUTF8String(buf, entry.getLeft().toString());
buf.writeShort(entry.getRight());
}
}
}
}
@@ -96,6 +96,8 @@ public final class WizardryBlocks {
@SubscribeEvent
public static void register(RegistryEvent.Register<Block> event){
BlockBookshelf.initBookProperties();
IForgeRegistry<Block> registry = event.getRegistry();
registerBlock(registry, "arcane_workbench", new BlockArcaneWorkbench().setHardness(1.0F).setCreativeTab(WizardryTabs.WIZARDRY));