From 48a350b63a00b0d65a14be60b6f5a56878686144 Mon Sep 17 00:00:00 2001
From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com>
Date: Fri, 15 May 2020 22:52:32 +0100
Subject: [PATCH] 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
---
.../java/electroblob/wizardry/Settings.java | 7 ++
.../java/electroblob/wizardry/Wizardry.java | 5 +
.../wizardry/block/BlockBookshelf.java | 103 +++++++++++++++---
.../client/model/BakedModelBookshelf.java | 10 +-
.../wizardry/client/model/ModelBookshelf.java | 20 +++-
.../inventory/ContainerBookshelf.java | 46 +++++++-
.../wizardry/packet/PacketSyncSettings.java | 30 +++--
.../wizardry/registry/WizardryBlocks.java | 2 +
.../assets/ebwizardry/lang/en_gb.lang | 2 +
.../assets/ebwizardry/lang/en_us.lang | 2 +
.../ebwizardry/textures/blocks/books_blue.png | Bin 0 -> 1977 bytes
.../textures/blocks/books_brown.png | Bin 0 -> 1926 bytes
.../textures/blocks/books_enchanted.png | Bin 0 -> 1991 bytes
.../textures/blocks/books_purple.png | Bin 0 -> 1944 bytes
.../blocks/{books.png => books_red.png} | Bin
15 files changed, 186 insertions(+), 41 deletions(-)
create mode 100644 src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png
create mode 100644 src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png
create mode 100644 src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png
create mode 100644 src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png
rename src/main/resources/assets/ebwizardry/textures/blocks/{books.png => books_red.png} (100%)
diff --git a/src/main/java/electroblob/wizardry/Settings.java b/src/main/java/electroblob/wizardry/Settings.java
index cf3db7a4..89b1cf32 100644
--- a/src/main/java/electroblob/wizardry/Settings.java
+++ b/src/main/java/electroblob/wizardry/Settings.java
@@ -296,6 +296,8 @@ public final class Settings {
Wizardry.MODID + ":acacia_bookshelf",
Wizardry.MODID + ":dark_oak_bookshelf"
);
+ /** [Synchronised] List of registry names of items that can be placed in a bookshelf. */
+ public Pair[] 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);
diff --git a/src/main/java/electroblob/wizardry/Wizardry.java b/src/main/java/electroblob/wizardry/Wizardry.java
index 0c9d7b50..db2444c0 100644
--- a/src/main/java/electroblob/wizardry/Wizardry.java
+++ b/src/main/java/electroblob/wizardry/Wizardry.java
@@ -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();
diff --git a/src/main/java/electroblob/wizardry/block/BlockBookshelf.java b/src/main/java/electroblob/wizardry/block/BlockBookshelf.java
index 267cc4b1..53935b17 100644
--- a/src/main/java/electroblob/wizardry/block/BlockBookshelf.java
+++ b/src/main/java/electroblob/wizardry/block/BlockBookshelf.java
@@ -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, ResourceLocation> BOOK_TEXTURE_MAP = new HashMap<>();
+ private static ImmutableList- bookItems;
+ private static ImmutableList 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 {
+ /** Returns the list of book textures, in ID order. */
+ public static ImmutableList 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 {
+
+ 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
- itemListBuider = ImmutableList.builder();
+ ImmutableList.Builder textureListBuilder = ImmutableList.builder();
+
+ for(Map.Entry, 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
+ * must 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
- 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.
diff --git a/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java b/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java
index c100b369..af18c319 100644
--- a/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java
+++ b/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java
@@ -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 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 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 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);
}
}
diff --git a/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java b/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java
index 5ece23a0..a32b7d2b 100644
--- a/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java
+++ b/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java
@@ -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
- 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);
+ }
}
}
diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java
index 55958461..24b0be25 100644
--- a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java
+++ b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java
@@ -41,6 +41,7 @@ public class PacketSyncSettings implements IMessageHandler {
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 {
}
@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 {
settings.replaceVanillaFireballs = buf.readBoolean();
settings.forfeitChance = buf.readFloat();
settings.bookshelfSearchRadius = buf.readInt();
- int length = buf.readInt();
- List> entries = new ArrayList<>();
- for(int i=0; i {
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 entry : settings.bookshelfBlocks){
+ writeMetaItems(buf, settings.bookshelfBlocks);
+ writeMetaItems(buf, settings.bookItems);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Pair[] readMetaItems(ByteBuf buf){
+ int length = buf.readInt();
+ List> entries = new ArrayList<>();
+ for(int i=0; i[] items){
+ buf.writeInt(items.length);
+ for(Pair entry : items){
ByteBufUtils.writeUTF8String(buf, entry.getLeft().toString());
buf.writeShort(entry.getRight());
}
}
+
}
}
diff --git a/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java b/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java
index b126b19c..3d63775f 100644
--- a/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java
+++ b/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java
@@ -96,6 +96,8 @@ public final class WizardryBlocks {
@SubscribeEvent
public static void register(RegistryEvent.Register event){
+ BlockBookshelf.initBookProperties();
+
IForgeRegistry registry = event.getRegistry();
registerBlock(registry, "arcane_workbench", new BlockArcaneWorkbench().setHardness(1.0F).setCreativeTab(WizardryTabs.WIZARDRY));
diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang
index cee7452f..adcf6990 100644
--- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang
+++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang
@@ -1238,6 +1238,8 @@ config.ebwizardry.bow_item_whitelist=Bow Item Whitelist
config.ebwizardry.bow_item_whitelist.tooltip=List of registry names of items which should count as bows for imbuement spells. Most bows should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:shortbow).
config.ebwizardry.bookshelf_blocks=Bookshelf Blocks
config.ebwizardry.bookshelf_blocks.tooltip=List of registry names of blocks that count as bookshelves for the arcane workbench and lectern. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. ebwizardry:oak_bookshelf).
+config.ebwizardry.book_items=Book Items
+config.ebwizardry.book_items.tooltip=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).
config.ebwizardry.bookshelf_search_radius=Bookshelf Search Radius
config.ebwizardry.bookshelf_search_radius.tooltip=The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it.
config.ebwizardry.currency_items=Currency Items
diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang
index f3e9bde0..fc4a1010 100644
--- a/src/main/resources/assets/ebwizardry/lang/en_us.lang
+++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang
@@ -1238,6 +1238,8 @@ config.ebwizardry.bow_item_whitelist=Bow Item Whitelist
config.ebwizardry.bow_item_whitelist.tooltip=List of registry names of items which should count as bows for imbuement spells. Most bows should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:shortbow).
config.ebwizardry.bookshelf_blocks=Bookshelf Blocks
config.ebwizardry.bookshelf_blocks.tooltip=List of registry names of blocks that count as bookshelves for the arcane workbench and lectern. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. ebwizardry:oak_bookshelf).
+config.ebwizardry.book_items=Book Items
+config.ebwizardry.book_items.tooltip=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).
config.ebwizardry.bookshelf_search_radius=Bookshelf Search Radius
config.ebwizardry.bookshelf_search_radius.tooltip=The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it.
config.ebwizardry.currency_items=Currency Items
diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png
new file mode 100644
index 0000000000000000000000000000000000000000..07235139f0fc93bbe2df1a4ff7b77eb3ad6cc2b1
GIT binary patch
literal 1977
zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-k5q+3lmsP~D-;yvr)B1(
zDwI?fq$;FVWTr7NRNOin9=_?d8PDJ6TANsy5*Vi+wwf%P^3zghQf==2>9aoxX6kq;
zbe08VT>k%~cElg-oM)W0hQ
zFJw!ORoQ*1`jK+sukhk4=UskUP2^X;ru<-11q){`<2M7dp9Vs9lkWc0yR9QV=~%k(
zt4RBEe~Osv((LBA@Yw~FR9I~@Y-=e`eXaE8+OkE9i)Q_!X74wg)pv1w=}p!dGJS#5HDB|-KK13=
zEztt^KQ=)}j+Lt4>6)^lV{w~;_6}{;qyrWw0vG!<|B})UUGZW^@sSfTkt?JGL=#lj
zHB3Hob~c;4=Oq`$Vpj`}6*aG&ymO)+X=$>U2Q|M?G*Hg@8NU7J$wh`~PJ)qpYgzYJ
zq)Ba=aY9hiJA%z6LuUD}rw?A&?NwXH#PYXj^PvMdr=KwV=w9w#aFQu%!-eEKoIV?6
z*$Mmy->ZDz+#gr_$FTYubJNP8*srEnBDOA@
zC2gL0;;~p%&aI%+bFG|K?tWMOVq>_zuHe&iPzGc^6@esZSe9s7yBkH2wloff`$a_a@3!!JX$PuLxp6KOoXZP%ls
zqInk{tEO7Vr}6J`+x&da+R8QN%dT+0~Duy(e0$K0esX
zus|lBq1kjf}dFHU*{mfx8!
zr||S`vishgw@S+X{E}N0x4u2}u*&D|ZdUs`^v^H0oc2D-JWkB*QM+W(yJrQop
z}Z->e;leZuRxWJGx(be_1TE_sEJ${!8vT+k{iR
z?&keJwB*0wpXsGVI{ps-Z(Q%5v1sR-sq^euzPNv=wJqX$a^>v)8E*nIo=Qw8J+b!9
z{mWNvoSV9KFa6e7_qpkM`Qj(4#}%4aXZ)QiU%zD5PrKfETP9EQvz&bE%VR0$_p5Hj
zU;lmj_10(0_on6lTh_p5RTz?0I{oO6c#*!U56-%`8QtP9izY@KUilzH@B6>H8uOaB
z^Zeac+P?Ugtmk~~7sr{rjwiHlFZ+|4cYVR`{kq=P(+ln1zS+L{!r}LuTAyv`
z+^Tx{V#$kD$3OcY&}5z$;Q#JjQ45u)g*4e`52M$V1VQyUv=_-*t{}sp09Io9(~)-_ERO&)@#-+uYBXeNXnw
zU5>SWTRBUszSYdNN}0!X(f)EZzkx!g~fick2#WBR<^wvp+9!!BU
z$Ljr$y!I68oR{ppIoY?@$=h^?tFvAc>!nG~-p$cBrvx;6duKbFSedc#=JH+=a0#gL
zSa|1W-vg;9dj6lcEX=Qd|NH;_-~ac&7yisG-Iu%Xk&}hls_RQP?Ch8lIH}WO*StqH
z|9u!V6klk#Zrjk6IQn>Ih(sprGPQ)h*q9anbDkbJu_rh{{X
zj`0Nk`kV9bIey)x%$gt0|H$=g_-hSO8%LL87t_*eSx;BLK6mcqI%a{(CP}lN%~z0_
zdAKxjPKRFIY=xMp-Opd{JpD9RE8xiPyyNBNHC48Axu>NnrHV8p*)>lITwkdrxO(d@
zh80ta?^zwKY0FxBuRbBd#k^Bb>*<`uuQeGi8r@P6E{r3g0aWl}9(&85k#Qlalz>&la>y{U6^4-p$@iuV@un&17I;VDNPHb6Mw<&;$Uw
CroAr!
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfc03dc340c3d85293a427927fea1247cb2e3e0c
GIT binary patch
literal 1926
zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-d#gesN`ey06$*;-(=u~X
z6-p`#QWa7wGSe6sDsHWfh@SLj1>c`^(akI>0v%5ihbCUe50P(~4Ch;DqxdakQl(LI}Sx9IFWqL%lxe}DZQb$gRy
zX3agPn~#6Lo1b`ckq(
zyzS86zhQo#Cd^LvQqT9-ZZBB>$5k#>;>k;^HNDHfuiCqP>+Uzq+ho2iI~SJ5{disC
zT1)K*eHBMu8GPFzoagEx@*}Wya_kOn)uanIP6jXanS1Mxm{!A!orOoH@7%CBNg-sy
z#2pP0MRj+sSnhhVD)>U|13tc>eH$;tn4Nvfr75;<3EMsA0Jj;R>hC_A?6|kTL1|Of
zZLS>^E>ZzJ&pLWkd6<>2Y!6J8`f+Fd^#@lQ9cQM60VT-)S{YSm3l|Qxck*kV_$W#@}9R^A|EfQ{SXD_HG+Iwg5VCWi_nu3EXomm6+Q+ZPgD
zm}g?D96$A{)YszLt6OfKdQ<$#$)M}%yW6wo=1ee|?V0(#ddsUEMT2hM;;PfC%oGZGM(m|uV}Jf;J>cZ
zXW`rCuKXAE9cS^s>9ClyXWFWzYZy-)C{t|r_M7T9Vb$`yjo;0mSy(S!;52R5w6^K`
zIn#G^EtsElMlbc7^qI+TJuTArRsDM|aaQ<*^c%Tl9wyu~)33Zw-SUjH{pHi2W^d}Y
z9gHcOaM*tNyEVK&vw3+R=7b;WQ^?sfzqdWQr?@OK$v-^EJiWC|r*#he+3+Cu
zvQ6agn^W?&vu}BCIk0~2v(OI*ytDOnm9Dbwu8T4F)_Lsi`@dg2j9oUpncNv=)p=&x
z>9;N?W=C$_&f!^?zwq&laN*ZI|K2@_yw6|t)v)~Kch5U_0{46qxw1cf%KLvMh9kJle9-_Ecow^dOz$y>XMK9C)yia+I{hsk*mupUk;O{!On#S_l{4R
zesBLvd>Snl>Fp>e&0OgpYMi#1_lPUByV>YhW`wg7*?dJ
zZ!2eDVBjq9h%9Dc&{GCs#)_r(Wef}q>?NMQuIx`(`GrmSy0YhWF)%PlmbgZgIOpf)
zrskC}I2WZRmZYXAlxLP?D7bt2281{Ai8C-ThI+aQNDrnGNanSRTQUcqrzw^>#v$ET2UuH3f
zAO8Jzam7`2TiMv}^AyB5H|!CakriQm$Lzb_H{XU0I>OVQzT{N$?vBtqBlK}O=aOH~
zZOuN>PYie73{y5V;Qqy(hJq3#UBsmJv`I5b*p{Dl&)VDdFQ7ed3IkvEL0n_OV3=9kmp00i_>zopr0DnB3n*aa+
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe1be1ab3b2e187db4a5aec75f72cb3de19b01f4
GIT binary patch
literal 1991
zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-7gU8rlmsP~D-;yvr)B1(
zDwI?fq$;FVWTr7NRNPt{9y#f;72ludn$I{`b}$})DD^SV;Q0I}N;gx(cdt&GCnBWT
z;VsnJqH+2E@AC%#4<0<%^-iq91nl~&
z@$rWBo1SkqZ@%_SPUQDz&a~G*bzS@?&ygav9cebgmbZj&7;iY4u5dg(ZU6k!anpLW
zURH8`-6H?|ZxOSeasEBkKEDe&+rgroQbqvf|mFr52x*s9mr3yF>N(<`vnRtJ}&_
ztFpg#SqQIV=)AGT@2KVk7grfmmdJyVo}G@575saRcwenv6T*10_<75uotqYCC5TK|
z7~`m8Y40yJXU?u?EG6MB%pGMl$qP-_u6wD)sg|e0cF|d)=fczfR%iXCdJS1!Q=Wbk
zE%+EB72xx%qes<*S=pp7_;AsO(tUE1!`aym7i~^xygAL1>4koy+`*?}7x{RZPw6Ij
zz2#vLXLMwcC`vfs_M%Pc=8+sBK^aX24Q4Cr^Q>C}IPNe^+FLC6#DR5&bgj_ZWil1B
zCvrHt6kOcFl6NI_!qZxZKPvhi3wdIH2{`d+dR&;WxH~w}aG8a1mY?2Fksu#U&r4H!
zEj|jVcnJwkU9*Ny(p=-qibbm|lP~p5G_FcFIkB^#@aXh_Bv03_X)!lL!fSa=uZ1kl
zI@>!t`$A#2u4(42qUC)7ulBXeRlmBl^?74}blwj^H<`>60mh%s-dgze?H|_cl8DDg
zujI_gyO!YlQmQGc
zHRrvPJHz_;(f-4;83q638>Fk1+9$l5yZwDcc4h60J4qR*3aqYLt;pXoCDKTIV%*%>
zp(js-mtKGNy?A=kuT`nvB5M^aj-L8Z`#ns$dEF|mx!>isZp#u#+ge!q{^m@j>kI12
z4+?%Va`oT)#;^0>WkY4NI~$+P;F@K6h1Vr!V*GMhhr_Ep_qyM|p~C-q_1a`*SFRPw
zro4NDFRnW{+w{|)eXp6TpKna(x%EnNkKLal-QKqx+l77hzUhdzefBISyYKt2-2Sq|
zPC<9F56?O;lw^D0M#kGj^D~cTadXPyOwsfp``F2CcbEA-edFrDcH`QclRM^!
zc4TK-Z++ZcIP>VY%{s#~j1bLRPj{eWWo3x5vd*t1ap7nF;0=pL{|9Az+e@WT4z{%oc$lKM)^S*rX
z^m}Gr^-D$l^V)eX+qZtLkp3T(^m_VBp1Bj|EWXR{`jv0_=X<}|gQd%sxp&NZE>Y|9
zKlsKk;Yw!d!^_%5!)MpzCa-hL-x&R*_*Q-3q@Rsb<{8|*Tw=L%$)oc}3-5Pqstw(f
z;2*=c|J%y{^Al&yU;T3P44(>*+nE*lZTIKfDmeXUF-!XWZ2jrV{1SyN|J0Vqzc^7N
zCbaLpdcww?2j+a^vdpQM`}xo-Ctmi~iPmL9tnP&mH{t{Of6-s>}Ts&1DYGbN?&5
zHEP?=gySyp2KF%}zt7spr2h}P*XMidBfplq`FGuXW82sL&tHaRe3*VB*zdOYj~NAb
zm)I}ooLB#jf&H=xt~0noX1tS@V?NU?S+-rCzs5(
zet9?Z?}m`sNypoI_RH^0s;)INu{{27^`Go8rgeqwJWB7L*Zqo`_4WRmA9w%Bb2z&f
z^_rS2VqjokOY(MiVffE*iD5;m`nGZg1_sUokH}&M20djEW~^9hU&g?|z+U3%>&pIw
zm0#FgsjGFuYX$}e$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj3e?p`-O`!e_&Y1`#S{
z&7uDpgp)#*41eq}7thVF&W~Q_xF=9V*5>xf-*Z0OJ-6(ac$xV*PiJ=dllh9t)-K0?
zPcU*Q4i<>z-?(>!DnpE-$8J60%ZHy7Mb^Zw^vil{%`oScIKv@kwr58*k9GQePjTTb
zZ+&3A>IWPDwA0C2tFsPo(O|!ydfM{jqvlEc6D*56`o1gwW`0r>xrAqm{De)PSDm?6G)Sq2HMpb?5$lL({#Bl43VGsd~o5
z-1^(D5@&1sxI^ks+(UQa(|j`|uDEQYgU*CNz1^1gxZ30Lpg3dj
MboFyt=akR{06dbsQUCw|
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png
new file mode 100644
index 0000000000000000000000000000000000000000..af1439faa07aac5676dcd3770aefb25ddb125b25
GIT binary patch
literal 1944
zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-msN#ClmsP~D-;yvr)B1(
zDwI?fq$;FVWTr7NRNPt{9y#fa2G5`8BE>8!8#^8+OzS9bw*05k9h+Sqe%!|TR6?Y}
zLLbgkTk8LpeCF2^TYc>33dPoh!ktfq(o3$a(>|{rX+Hn=(Rp*(ckZ+^aA^v=wz^F>
ze}eIs%G0OksXM=WujZ9oZy{&@#8&XNSwYmDww7s(yVcDq{Z;np?*3Z0CiUh7lY9CL
zbN=qzf8O9h`mw{FPaZJ4wl7bXPWE`A9W5@n-Ad+d>t%`O{k9h`@3st;v?~-A>Q0xC
zJM}B|kIg)ldH$Wpqs7#>*swDy_Lf)y5q%;LYLDq5i2A(L=#l>
z7^X33=Dm_p^%P~j!&)NX@$*ybvYRGnMS~>PEn$13=;J2wX@B15&Y<_o3p>`tZET9S
zG2)HmdD79Nx`I*gmGz1`wjX=uuWww<;Anq%O+>=B<1-jP$j{4rGKq7G!^Oq1oIV@h
zaSA9^Jb1{%DX@XD(z!7w#cU!+V+g~PhgRK>nKw-M9`jJedLl!?1lA3`h7*@*RmUZX
zI59j_mFPU6=E)XuuJVJr6>Iw@@%MYaA9P=D_ftlthlg9SEikZn%8Nz)ipN$M9?c24
zvhwXe8r
zOD1WDuRQTsDr-$t@Y!Ce6<1u$NRW$85Q71x&TepmfsW48aY=iFO)U!VTVeI;Y@
zG2xX9CG@W-^nRSvG8=QDci=PCDDhJHDbT39M
zT$HnGtw+=;#bCzNL_d~zhgm-@BQ@(X%@!=mvz}J6mi5S~)7kF4jnbD+Buq8;=F`gm
zywOQj{q6Fg!sYRsnx5Xty`~yib~hn<{z0t+E#7wxWB%P-{kBGD>zkit780SzlI9h!b3DyvT=_^Uhq!^%}ZG
zXE)B(+!3er)owv-P@3_RI4zEC*ZO65+USSfce%ga^!bef<2h!^wrf`C6?$*8j{a4(
zc~aaSuG9Rkf>F~7SL9i)Tk5KNqHBxN(kYj{tiHSw4cV>H+)^PQ{Mk9ce%Z>(^OxUe
zMosrNxT2Z2Xx61$JEP{T@iR{Uo%wF_to@UJCokHq)!wzc>9Wen+l2RW47SG;hV+pFXB4|
zO?02XWa3ZF{*LKCG>*pZs=U0pJ52X@SKzg7`PnBtW(t{EotU0oWBsjwfAg;??#~vx
z{8H#Yv+N*?(a-R$Tb3OZ{A%|jd`m#Z^b1pF1~;GSR{xx~uK!$|-~R4tmDj2yL$}WjTXA*0
z_N1R#&uh+~@6ObpV%k6F@5;BoV@ebrvqiq$`TtS=w0CEBG=5($ZPB%_<$tZ_F0KBb
zzAr21);!PgZ=aY|zA?fq}im
z)7O>#2`j&_vDO2lyLTBF7$i$vBTAg}b8}PkN*J7rQWHy3QxwWGOEMJPJ$(bh8~MZ;
z7#RILT^vI!PHzpe^*HPxQ5)Xueq_NH^;nrXrNspTyFz0^TnY*|#BznM(0ldbg751T
z$96qGS?e9Q&K~t_pA?(Hp6e7KB)yKKkVAY@#=5c(zU6z~@BV%7x7xaFv9c@k
zm~Gw=H=|w0UvaXZQ|RU$miyG?{1Xh@-hPsjNpCoEDvsTIwe5aOuDb>o-|ufUcUR0j
zE3@T1pUVYa<7wUrEHdc|PnPztFaMup{M!9s_UoA?Hw73H4Atf-n9e+A66(Zy+FknH
zzo(7d3|vbCGcB)kybw8!96Il~XOHCN_MI+wd+5d#AQ
NgQu&X%Q~loCIG{bgJ%E$
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_red.png
similarity index 100%
rename from src/main/resources/assets/ebwizardry/textures/blocks/books.png
rename to src/main/resources/assets/ebwizardry/textures/blocks/books_red.png