diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 8a8d24b2..f0718ed8 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -47,6 +47,7 @@ import com.minelittlepony.jumpingcastle.api.JumpingCastle; import com.minelittlepony.unicopia.advancements.UAdvancements; import com.minelittlepony.unicopia.block.ITillable; import com.minelittlepony.unicopia.command.Commands; +import com.minelittlepony.unicopia.enchanting.Pages; import com.minelittlepony.unicopia.enchanting.SpellRecipe; import com.minelittlepony.unicopia.forgebullshit.FBS; import com.minelittlepony.unicopia.inventory.gui.ContainerSpellBook; @@ -112,6 +113,8 @@ public class Unicopia implements IGuiHandler { } }; + Pages.instance().load(); + Biome.REGISTRY.forEach(UEntities::registerSpawnEntries); UClient.instance().posInit(event); diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/BasicCraftingEvent.java b/src/main/java/com/minelittlepony/unicopia/enchanting/BasicCraftingEvent.java deleted file mode 100644 index ff8a8230..00000000 --- a/src/main/java/com/minelittlepony/unicopia/enchanting/BasicCraftingEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.minelittlepony.unicopia.enchanting; - -import com.minelittlepony.unicopia.UItems; -import com.minelittlepony.unicopia.enchanting.PagesList.IPageEvent; -import com.minelittlepony.unicopia.spell.SpellRegistry; - -import net.minecraft.item.ItemStack; - -/** - * A basic event for unlocking a page when a gem is crafted for the given spell - */ -public class BasicCraftingEvent implements IPageEvent { - - private final String matched; - private final int pageIndex; - - public BasicCraftingEvent(int page, String effectName) { - matched = effectName; - pageIndex = page; - } - - @Override - public boolean matches(IPageOwner prop, ItemStack stack) { - return stack.getItem() == UItems.spell && SpellRegistry.getKeyFromStack(stack).equals(matched); - } - - @Override - public int getPage(int stackSize) { - return pageIndex; - } - -} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/CompoundCondition.java b/src/main/java/com/minelittlepony/unicopia/enchanting/CompoundCondition.java new file mode 100644 index 00000000..8d8d2e02 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/CompoundCondition.java @@ -0,0 +1,56 @@ +package com.minelittlepony.unicopia.enchanting; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class CompoundCondition implements IUnlockCondition { + + final Op operation; + + final List> conditions = Lists.newArrayList(); + + CompoundCondition(JsonObject json) { + require(json, "operation"); + require(json, "conditions"); + + operation = Op.valueOf(json.get("operation").getAsString().toUpperCase()); + + json.get("conditions").getAsJsonArray().forEach(this::addElement); + } + + void addElement(JsonElement element) { + JsonObject obj = element.getAsJsonObject(); + + conditions.add(Pages.instance().createCondition(obj)); + } + + @Override + public boolean accepts(IUnlockEvent event) { + return true; + } + + @Override + public boolean matches(IPageOwner owner, IUnlockEvent event) { + return operation.test.apply(conditions.stream(), condition -> condition.accepts(event) && condition.matches(owner, event)); + } + + enum Op { + AND(Stream::allMatch), + OR(Stream::anyMatch); + + final Test test; + + Op(Test test) { + this.test = test; + } + + interface Test { + boolean apply(Stream> stream, Predicate> predicate); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IConditionFactory.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IConditionFactory.java new file mode 100644 index 00000000..ccfe32e9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IConditionFactory.java @@ -0,0 +1,8 @@ +package com.minelittlepony.unicopia.enchanting; + +import com.google.gson.JsonObject; + +@FunctionalInterface +public interface IConditionFactory { + IUnlockCondition create(JsonObject json); +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IPage.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IPage.java new file mode 100644 index 00000000..e720c794 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IPage.java @@ -0,0 +1,40 @@ +package com.minelittlepony.unicopia.enchanting; + +import net.minecraft.util.ResourceLocation; + +/** + * A spellbook page + */ +public interface IPage extends Comparable { + /** + * Gets the index. + * This is the position the page appears in the book gui. + */ + int getIndex(); + + /** + * The unique name of this page. + */ + ResourceLocation getName(); + + /** + * Tests unlock conditions for this page. + * Returns true if the owner is permitted to read this page. + */ + boolean canUnlock(IPageOwner owner, IUnlockEvent event); + + /** + * Gets the texture. + * This is what's shown when this page is opened in the book gui. + */ + ResourceLocation getTexture(); + + /** + * The default state. + */ + PageState getDefaultState(); + + IPage next(); + + IPage prev(); +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IPageOwner.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IPageOwner.java index b230cb68..399387c5 100644 --- a/src/main/java/com/minelittlepony/unicopia/enchanting/IPageOwner.java +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IPageOwner.java @@ -1,32 +1,32 @@ package com.minelittlepony.unicopia.enchanting; -import java.util.List; +import java.util.Map; import javax.annotation.Nonnull; import com.minelittlepony.unicopia.network.ITransmittable; +import net.minecraft.util.ResourceLocation; + +/** + * Interface for things that own and can unlock pages. + * + */ public interface IPageOwner extends ITransmittable { @Nonnull - List getUnlockedPages(); + Map getPageStates(); - default boolean hasPageUnlock(int pageIndex) { - return getUnlockedPages().contains(pageIndex); - } - - default boolean unlockPage(int pageIndex) { - if (!hasPageUnlock(pageIndex)) { - if (getUnlockedPages().add(pageIndex)) { - sendCapabilities(true); - - return true; - } + default void setPageState(IPage page, PageState state) { + if (state == PageState.LOCKED) { + getPageStates().remove(page.getName()); + } else { + getPageStates().put(page.getName(), state); } - return false; + sendCapabilities(true); } - default boolean hasUnlockedPages() { - return getUnlockedPages().size() > 0; + default PageState getPageState(IPage page) { + return getPageStates().getOrDefault(page.getName(), page.getDefaultState()); } } diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IPageUnlockListener.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IPageUnlockListener.java index 500f9bc8..706c2a9e 100644 --- a/src/main/java/com/minelittlepony/unicopia/enchanting/IPageUnlockListener.java +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IPageUnlockListener.java @@ -1,5 +1,12 @@ package com.minelittlepony.unicopia.enchanting; +@FunctionalInterface public interface IPageUnlockListener { - public void onPageUnlocked(); + /** + * Called when a page is unlocked. + * + * @param page The page that has been unlocked + * @return True to allow, false to block. + */ + boolean onPageUnlocked(IPage page); } diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockCondition.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockCondition.java new file mode 100644 index 00000000..48fb4b71 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockCondition.java @@ -0,0 +1,30 @@ +package com.minelittlepony.unicopia.enchanting; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +/** + * A PageEvent for determining when certain pages must be unlocked. + */ +public interface IUnlockCondition { + + /** + * Returns true if event instanceof T + */ + default boolean accepts(IUnlockEvent event) { + return true; + } + + /** + * Checks if this event's conditions are met. + * @param prop PlayerExtension for the player doing the crafting + * @param stack ItemStack crafted + */ + boolean matches(IPageOwner owner, T event); + + default void require(JsonObject json, String memberName) { + if (!json.has(memberName)) { + throw new JsonParseException(String.format("%s condition must have a %s", getClass().getSimpleName(), memberName)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockEvent.java b/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockEvent.java new file mode 100644 index 00000000..abcc5214 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/IUnlockEvent.java @@ -0,0 +1,5 @@ +package com.minelittlepony.unicopia.enchanting; + +public interface IUnlockEvent { + +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/MultiPageUnlockEvent.java b/src/main/java/com/minelittlepony/unicopia/enchanting/MultiPageUnlockEvent.java deleted file mode 100644 index 6b9f1357..00000000 --- a/src/main/java/com/minelittlepony/unicopia/enchanting/MultiPageUnlockEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.minelittlepony.unicopia.enchanting; - -import com.minelittlepony.unicopia.enchanting.PagesList.IPageEvent; - -import net.minecraft.item.ItemStack; - -/** - * An unlock event that requires other pages to be unlocked before it too can be unlocked. - * - */ -public class MultiPageUnlockEvent implements IPageEvent { - - private final int pageIndex; - - private final int[][] otherPageIndeces; - - public MultiPageUnlockEvent(int page, int[]... otherPages) { - pageIndex = page; - otherPageIndeces = otherPages; - } - - @Override - public boolean matches(IPageOwner prop, ItemStack stack) { - for (int i = 0; i < otherPageIndeces.length; i++) { - if (!checkPageUnlockSet(prop, otherPageIndeces[i])) return false; - } - return true; - } - - private boolean checkPageUnlockSet(IPageOwner prop, int[] pages) { - for (int i = 0; i < pages.length; i++) { - if (prop.hasPageUnlock(pages[i])) return true; - } - return false; - } - - @Override - public int getPage(int stackSize) { - return pageIndex; - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/PageInstance.java b/src/main/java/com/minelittlepony/unicopia/enchanting/PageInstance.java new file mode 100644 index 00000000..8d1a4941 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/PageInstance.java @@ -0,0 +1,100 @@ +package com.minelittlepony.unicopia.enchanting; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; +import com.google.gson.annotations.Expose; + +import net.minecraft.util.ResourceLocation; + +class PageInstance implements IPage { + + int index; + + @Nonnull + ResourceLocation name; + + @Nonnull + ResourceLocation texture; + + @Nullable + IUnlockCondition condition; + + @Expose + PageState state = PageState.LOCKED; + + PageInstance(ResourceLocation id, JsonObject json) { + this.name = id; + + if (json.has("state")) { + state = PageState.of(json.get("state").getAsString()); + } + + if (json.has("condition")) { + condition = Pages.instance().createCondition(json.get("condition").getAsJsonObject()); + } + + String full = json.get("texture").getAsString(); + String[] loc = full.split(":"); + if (loc.length < 2) { + loc = new String[] { "minecraft", full }; + } + + if ("minecraft".equals(loc[0]) && !"minecraft".equals(id.getNamespace())) { + loc[0] = id.getNamespace(); + } + + texture = new ResourceLocation(loc[0], String.format("textures/pages/%s.png", loc[1])); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public ResourceLocation getName() { + return name; + } + + @Override + public ResourceLocation getTexture() { + return texture; + } + + @Override + public PageState getDefaultState() { + return state; + } + + @Override + public boolean canUnlock(IPageOwner owner, IUnlockEvent event) { + return condition == null || condition.accepts(event) && condition.matches(owner, event); + } + + @Override + public IPage next() { + int i = Math.min(Pages.instance().getTotalPages() - 1, index + 1); + return Pages.instance().getByIndex(i); + } + + @Override + public IPage prev() { + if (index <= 0) { + return this; + } + + return Pages.instance().getByIndex(index - 1); + } + + @Override + public int compareTo(IPage o) { + return getIndex() - o.getIndex(); + } + + @Override + public boolean equals(Object o) { + return o instanceof IPage && getName().equals(((IPage)o).getName()); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/PageState.java b/src/main/java/com/minelittlepony/unicopia/enchanting/PageState.java new file mode 100644 index 00000000..6f2efa9e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/PageState.java @@ -0,0 +1,24 @@ +package com.minelittlepony.unicopia.enchanting; + +public enum PageState { + LOCKED, + UNREAD, + READ; + + public boolean isLocked() { + return this == LOCKED; + } + + public boolean isUnread() { + return this == UNREAD; + } + + public static PageState of(String s) { + try { + if (s != null) + return valueOf(s.toUpperCase()); + } catch (Throwable e) {} + + return PageState.LOCKED; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/PageStateCondition.java b/src/main/java/com/minelittlepony/unicopia/enchanting/PageStateCondition.java new file mode 100644 index 00000000..dfa4062e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/PageStateCondition.java @@ -0,0 +1,31 @@ +package com.minelittlepony.unicopia.enchanting; + +import com.google.gson.JsonObject; + +import net.minecraft.util.ResourceLocation; + +public class PageStateCondition implements IUnlockCondition { + + ResourceLocation page; + + PageState state; + + PageStateCondition(JsonObject json) { + require(json, "page"); + require(json, "state"); + + page = new ResourceLocation(json.get("page").getAsString()); + state = PageState.of(json.get("state").getAsString()); + } + + @Override + public boolean matches(IPageOwner owner, IUnlockEvent event) { + IPage ipage = Pages.instance().getByName(page); + + if (ipage != null) { + return owner.getPageState(ipage) == state; + } + + return false; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/Pages.java b/src/main/java/com/minelittlepony/unicopia/enchanting/Pages.java new file mode 100644 index 00000000..8b907a9c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/Pages.java @@ -0,0 +1,97 @@ +package com.minelittlepony.unicopia.enchanting; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.minelittlepony.util.AssetWalker; + +import net.minecraft.util.ResourceLocation; + +public class Pages { + + private static final Pages instance = new Pages(); + + public static Pages instance() { + return instance; + } + + private final Map pages = Maps.newHashMap(); + private final List pagesByIndex = Lists.newArrayList(); + + private final Map conditionFactories = Maps.newHashMap(); + + private final AssetWalker assets = new AssetWalker(new ResourceLocation("unicopia", "pages"), this::addPage); + + Pages() { + registerConditionFactory("unicopia:compound_condition", CompoundCondition::new); + registerConditionFactory("unicopia:page_state", PageStateCondition::new); + registerConditionFactory("unicopia:spell_crafting", SpellCraftingEvent.Condition::new); + } + + public void load() { + pages.clear(); + pagesByIndex.clear(); + assets.walk(); + + int index = 0; + for (IPage ipage : pages.values()) { + ((PageInstance)ipage).index = index++; + pagesByIndex.add(ipage); + } + } + + void addPage(ResourceLocation id, JsonObject json) throws JsonParseException { + pages.put(id, new PageInstance(id, json)); + } + + @SuppressWarnings("unchecked") + IUnlockCondition createCondition(JsonObject json) { + String key = json.get("key").getAsString(); + + return (IUnlockCondition)conditionFactories.get(key).create(json); + } + + @Nullable + public IPage getByName(ResourceLocation name) { + return pages.get(name); + } + + @Nullable + public IPage getByIndex(int index) { + return pagesByIndex.get(index); + } + + public Stream getUnlockablePages(Predicate predicate) { + return pages.values().stream().filter(predicate); + } + + public void triggerUnlockEvent(IPageOwner owner, IUnlockEvent event, @Nullable IPageUnlockListener unlockListener) { + pages.values().stream() + .filter(page -> page.canUnlock(owner, event)) + .forEach(page -> unlockPage(owner, page, unlockListener)); + } + + public void unlockPage(IPageOwner owner, IPage page, @Nullable IPageUnlockListener unlockListener) { + if (owner.getPageState(page).isLocked()) { + if (unlockListener == null || unlockListener.onPageUnlocked(page)) { + owner.setPageState(page, PageState.UNREAD); + } + } + } + + public void registerConditionFactory(String conditionType, IConditionFactory factory) { + conditionFactories.put(conditionType, factory); + } + + public int getTotalPages() { + return pages.size(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/PagesList.java b/src/main/java/com/minelittlepony/unicopia/enchanting/PagesList.java deleted file mode 100644 index c9e6ae83..00000000 --- a/src/main/java/com/minelittlepony/unicopia/enchanting/PagesList.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.minelittlepony.unicopia.enchanting; - -import java.util.ArrayList; -import java.util.List; - -import com.minelittlepony.unicopia.player.PlayerSpeciesList; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemStack; - -public class PagesList { - - private static final List unreadPages = new ArrayList(); - - private static final List pageEvents = new ArrayList(); - - private static int totalPages = 0; - - /** - * Sets the maximum number of pages displayed in the spellbook. - * Only allows widening. Total pages cannot be reduced. - */ - public static void setTotalPages(int pages) { - if (pages > totalPages) { - totalPages = pages; - } - } - - public static int getTotalPages() { - return totalPages; - } - - /** - * Registers an event for unlocking a page. - */ - public static void registerPageEvent(IPageEvent event) { - pageEvents.add(event); - } - - /** - * Marks a page as read - */ - public static void readPage(int pageIndex) { - unreadPages.remove(Integer.valueOf(pageIndex)); - } - - /** - * Checks if there are any pages after the given index that are unread - * Only useful on the client - */ - public static boolean hasUnreadPagesAfter(int pageIndex) { - for (Integer i : unreadPages) { - if (i > pageIndex) return true; - } - return false; - } - - /** - * Checks if there are any pages before the given index that are unread - * Only useful on the client - */ - public static boolean hasUnreadPagesBefore(int pageIndex) { - for (Integer i : unreadPages) { - if (i < pageIndex) return true; - } - return false; - } - - /** - * Checks if the given page has been read yet. - * Only of use on the client - */ - public static boolean isPageUnread(int pageIndex) { - return unreadPages.contains(pageIndex); - } - - private static boolean unlockPages(IPageOwner prop, ItemStack stack) { - boolean result = false; - if (stack != null && stack.getCount() > 0) { - for (IPageEvent i : pageEvents) { - if (i.matches(prop, stack)) { - int page = i.getPage(stack.getCount()); - if (page >= 0 && prop.unlockPage(page)) { - result |= unreadPages.add(page); - } - } - } - } - return result; - } - - /** - * Checks for, and unlocks any pages that can be unlocked by the given item for the given player - * @return True if a page was unlocked, false otherwise - */ - public static boolean unlockPage(EntityPlayer player, ItemStack stack) { - return unlockPages(PlayerSpeciesList.instance().getPlayer(player), stack); - } - - /** - * A PageEvent for determining when certain pages must be unlocked. - * - */ - public static interface IPageEvent { - /** - * Checks if this event's conditions are met. - * @param prop PlayerExtension for the player doing the crafting - * @param stack ItemStack crafted - */ - public boolean matches(IPageOwner prop, ItemStack stack); - - /** - * Gets the page number corresponding to the given stack for this event - */ - public int getPage(int stackSize); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/enchanting/SpellCraftingEvent.java b/src/main/java/com/minelittlepony/unicopia/enchanting/SpellCraftingEvent.java new file mode 100644 index 00000000..af2bac90 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/enchanting/SpellCraftingEvent.java @@ -0,0 +1,61 @@ +package com.minelittlepony.unicopia.enchanting; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; +import com.google.gson.annotations.Expose; +import com.minelittlepony.unicopia.item.ItemSpell; +import com.minelittlepony.unicopia.spell.SpellAffinity; +import com.minelittlepony.unicopia.spell.SpellRegistry; + +import net.minecraft.item.ItemStack; + +/** + * A basic event for unlocking a page when a gem is crafted for the given spell + */ +public class SpellCraftingEvent { + + public static void trigger(IPageOwner owner, ItemStack stack, @Nullable IPageUnlockListener unlockListener) { + Pages.instance().triggerUnlockEvent(owner, new Event(stack), unlockListener); + } + + static class Event implements IUnlockEvent { + final ItemStack stack; + + Event(ItemStack stack) { + this.stack = stack; + } + } + + static class Condition implements IUnlockCondition { + @Nonnull + SpellAffinity affinity; + + @Expose + String spell; + + Condition(JsonObject json) { + require(json, "affinity"); + require(json, "spell"); + + affinity = SpellAffinity.of(json.get("affinity").getAsString()); + spell = json.get("spell").getAsString(); + } + + @Override + public boolean accepts(IUnlockEvent event) { + return event instanceof Event; + } + + @Override + public boolean matches(IPageOwner prop, Event event) { + if (!event.stack.isEmpty() && event.stack.getItem() instanceof ItemSpell) { + return ((ItemSpell)event.stack.getItem()).getAffinity() == affinity + && SpellRegistry.getKeyFromStack(event.stack).equals(spell); + } + + return false; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntitySpellbook.java b/src/main/java/com/minelittlepony/unicopia/entity/EntitySpellbook.java index fcc2ea04..db1d0c4e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/EntitySpellbook.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/EntitySpellbook.java @@ -114,7 +114,7 @@ public class EntitySpellbook extends EntityLiving implements IMagicals { SoundType sound = SoundType.WOOD; world.playSound(posX, posY, posZ, sound.getBreakSound(), SoundCategory.BLOCKS, sound.getVolume(), sound.getPitch(), true); if (world.getGameRules().getBoolean("doTileDrops")) { - entityDropItem(new ItemStack(UItems.spellbook, 1), 0); + entityDropItem(new ItemStack(UItems.spellbook), 0); } } return false; diff --git a/src/main/java/com/minelittlepony/unicopia/inventory/gui/ContainerSpellBook.java b/src/main/java/com/minelittlepony/unicopia/inventory/gui/ContainerSpellBook.java index e5b8e0de..1d2998f9 100644 --- a/src/main/java/com/minelittlepony/unicopia/inventory/gui/ContainerSpellBook.java +++ b/src/main/java/com/minelittlepony/unicopia/inventory/gui/ContainerSpellBook.java @@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.inventory.InventorySpellBook; import com.minelittlepony.unicopia.inventory.slot.SlotEnchanting; import com.minelittlepony.unicopia.inventory.slot.SlotEnchantingResult; import com.minelittlepony.unicopia.item.ItemSpell; +import com.minelittlepony.unicopia.player.PlayerSpeciesList; import com.minelittlepony.unicopia.spell.SpellRegistry; import net.minecraft.entity.player.EntityPlayer; @@ -64,7 +65,7 @@ public class ContainerSpellBook extends Container { addSlotToContainer(new SlotEnchanting(craftMatrix, 2, 180, 134)); addSlotToContainer(new SlotEnchanting(craftMatrix, 3, 231, 120)); addSlotToContainer(new SlotEnchanting(craftMatrix, 4, 232, 65)); - addSlotToContainer(resultSlot = new SlotEnchantingResult(listener, player, craftMatrix, craftResult, 0, 196, 92)); + addSlotToContainer(resultSlot = new SlotEnchantingResult(listener, PlayerSpeciesList.instance().getPlayer(player), craftMatrix, craftResult, 0, 196, 92)); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/inventory/gui/GuiSpellBook.java b/src/main/java/com/minelittlepony/unicopia/inventory/gui/GuiSpellBook.java index 9514aa86..8b6873d7 100644 --- a/src/main/java/com/minelittlepony/unicopia/inventory/gui/GuiSpellBook.java +++ b/src/main/java/com/minelittlepony/unicopia/inventory/gui/GuiSpellBook.java @@ -4,8 +4,11 @@ import java.io.IOException; import org.lwjgl.opengl.GL11; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.enchanting.IPage; import com.minelittlepony.unicopia.enchanting.IPageUnlockListener; -import com.minelittlepony.unicopia.enchanting.PagesList; +import com.minelittlepony.unicopia.enchanting.PageState; +import com.minelittlepony.unicopia.enchanting.Pages; import com.minelittlepony.unicopia.inventory.slot.SlotEnchanting; import com.minelittlepony.unicopia.player.IPlayer; import com.minelittlepony.unicopia.player.PlayerSpeciesList; @@ -21,20 +24,21 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.ResourceLocation; public class GuiSpellBook extends GuiContainer implements IPageUnlockListener { - private static int currentPage = 0; - private static ResourceLocation spellBookPageTextures = new ResourceLocation("unicopia", "textures/gui/container/pages/page-" + currentPage + ".png"); + + private static IPage currentIPage; private static final ResourceLocation spellBookGuiTextures = new ResourceLocation("unicopia", "textures/gui/container/book.png"); private IPlayer playerExtension; + private PageButton nextPage; private PageButton prevPage; public GuiSpellBook(EntityPlayer player) { super(new ContainerSpellBook(player.inventory, player.world, new BlockPos(player))); player.openContainer = inventorySlots; - ((ContainerSpellBook)inventorySlots).setListener(this); + xSize = 405; ySize = 219; allowUserInput = true; @@ -51,6 +55,12 @@ public class GuiSpellBook extends GuiContainer implements IPageUnlockListener { buttonList.add(nextPage = new PageButton(1, x + 360, y + 160, true)); buttonList.add(prevPage = new PageButton(2, x + 20, y + 160, false)); + + if (currentIPage == null) { + currentIPage = Pages.instance().getByIndex(0); + } + + onPageChange(); } @Override @@ -58,45 +68,37 @@ public class GuiSpellBook extends GuiContainer implements IPageUnlockListener { initGui(); if (button.id == 1) { - nextPage(); + currentIPage = currentIPage.next(); } else { - prevPage(); + currentIPage = currentIPage.prev(); } + + onPageChange(); } - public void nextPage() { - if (currentPage == 0) { - playerExtension.unlockPage(1); - } - if (currentPage < PagesList.getTotalPages() - 1) { - currentPage++; - spellBookPageTextures = new ResourceLocation("unicopia", "textures/gui/container/pages/page-" + currentPage + ".png"); + protected void onPageChange() { + prevPage.visible = currentIPage.getIndex() > 0; + nextPage.visible = currentIPage.getIndex() < Pages.instance().getTotalPages() - 1; - onPageUnlocked(); - PagesList.readPage(currentPage); - } - } - - @Override - public void onPageUnlocked() { - if (PagesList.hasUnreadPagesAfter(currentPage)) { - nextPage.triggerShake(); - } - - if (PagesList.hasUnreadPagesBefore(currentPage)) { - prevPage.triggerShake(); + if (playerExtension.getPageState(currentIPage) == PageState.UNREAD) { + playerExtension.setPageState(currentIPage, PageState.READ); } } - public void prevPage() { - if (currentPage > 0) { - currentPage--; - spellBookPageTextures = new ResourceLocation("unicopia", "textures/gui/container/pages/page-" + currentPage + ".png"); + @Override + public boolean onPageUnlocked(IPage page) { + int i = currentIPage.compareTo(page); - onPageUnlocked(); - PagesList.readPage(currentPage); - } - } + if (i <= 0) { + prevPage.triggerShake(); + } + + if (i >= 0) { + nextPage.triggerShake(); + } + + return true; + } @Override protected void drawGradientRect(int left, int top, int width, int height, int startColor, int endColor) { @@ -128,10 +130,9 @@ public class GuiSpellBook extends GuiContainer implements IPageUnlockListener { @Override protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { - if (PagesList.getTotalPages() > 0) { - String text = (currentPage + 1) + "/" + PagesList.getTotalPages(); - fontRenderer.drawString(text, 203 - fontRenderer.getStringWidth(text)/2, 165, 0x0); - } + String text = String.format("%d / %d", currentIPage.getIndex() + 1, Pages.instance().getTotalPages()); + + fontRenderer.drawString(text, 203 - fontRenderer.getStringWidth(text)/2, 165, 0x0); } @Override @@ -147,12 +148,18 @@ public class GuiSpellBook extends GuiContainer implements IPageUnlockListener { GlStateManager.enableBlend(); GL11.glDisable(GL11.GL_ALPHA_TEST); - if (playerExtension.hasPageUnlock(currentPage)) { - if (mc.getTextureManager().getTexture(spellBookPageTextures) != TextureUtil.MISSING_TEXTURE) { + if (playerExtension.getPageState(currentIPage) != PageState.LOCKED) { + ResourceLocation texture = currentIPage.getTexture(); + + if (mc.getTextureManager().getTexture(texture) != TextureUtil.MISSING_TEXTURE) { GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - mc.getTextureManager().bindTexture(spellBookPageTextures); + mc.getTextureManager().bindTexture(texture); drawModalRectWithCustomSizedTexture(left, top, 0, 0, xSize, ySize, 512, 256); + } else { + if (playerExtension.getWorld().rand.nextInt(100) == 0) { + Unicopia.log.fatal("Missing texture " + texture); + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/inventory/slot/SlotEnchantingResult.java b/src/main/java/com/minelittlepony/unicopia/inventory/slot/SlotEnchantingResult.java index 3e0cba79..375f124a 100644 --- a/src/main/java/com/minelittlepony/unicopia/inventory/slot/SlotEnchantingResult.java +++ b/src/main/java/com/minelittlepony/unicopia/inventory/slot/SlotEnchantingResult.java @@ -1,8 +1,9 @@ package com.minelittlepony.unicopia.inventory.slot; import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.enchanting.IPageOwner; import com.minelittlepony.unicopia.enchanting.IPageUnlockListener; -import com.minelittlepony.unicopia.enchanting.PagesList; +import com.minelittlepony.unicopia.enchanting.SpellCraftingEvent; import com.minelittlepony.unicopia.inventory.InventorySpellBook; import com.minelittlepony.unicopia.item.ItemSpell; import com.minelittlepony.unicopia.spell.SpellRegistry; @@ -14,14 +15,14 @@ import net.minecraft.util.NonNullList; public class SlotEnchantingResult extends SlotEnchanting { - private final EntityPlayer thePlayer; + private final IPageOwner owner; private final InventorySpellBook craftMatrix; private IPageUnlockListener listener; - public SlotEnchantingResult(IPageUnlockListener listener, EntityPlayer player, InventorySpellBook craftMatric, IInventory inventory, int index, int xPosition, int yPosition) { + public SlotEnchantingResult(IPageUnlockListener listener, IPageOwner owner, InventorySpellBook craftMatric, IInventory inventory, int index, int xPosition, int yPosition) { super(inventory, index, xPosition, yPosition); - thePlayer = player; + this.owner = owner; this.listener = listener; craftMatrix = craftMatric; } @@ -75,13 +76,7 @@ public class SlotEnchantingResult extends SlotEnchanting { @Override protected void onCrafting(ItemStack stack) { - if (PagesList.unlockPage(thePlayer, stack) && listener != null) { - listener.onPageUnlocked(); - } - - if (listener != null) { - listener.onPageUnlocked(); - } + SpellCraftingEvent.trigger(owner, stack, listener); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/item/ItemSpellbook.java b/src/main/java/com/minelittlepony/unicopia/item/ItemSpellbook.java index f1737de0..a5a723bc 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/ItemSpellbook.java +++ b/src/main/java/com/minelittlepony/unicopia/item/ItemSpellbook.java @@ -56,7 +56,6 @@ public class ItemSpellbook extends ItemBook { BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(this, dispenserBehavior); } - public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) { if (!world.isRemote && Predicates.MAGI.test(player)) { diff --git a/src/main/java/com/minelittlepony/unicopia/player/PlayerCapabilities.java b/src/main/java/com/minelittlepony/unicopia/player/PlayerCapabilities.java index c94fe9df..0fdf821b 100644 --- a/src/main/java/com/minelittlepony/unicopia/player/PlayerCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/player/PlayerCapabilities.java @@ -1,16 +1,16 @@ package com.minelittlepony.unicopia.player; -import java.util.List; +import java.util.Map; -import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.minelittlepony.model.anim.BasicEasingInterpolator; import com.minelittlepony.model.anim.IInterpolator; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.UEffects; import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.enchanting.PageState; import com.minelittlepony.unicopia.network.EffectSync; import com.minelittlepony.unicopia.network.MsgPlayerCapabilities; import com.minelittlepony.unicopia.spell.IMagicEffect; @@ -24,13 +24,13 @@ import net.minecraft.init.MobEffects; import net.minecraft.item.ItemFood; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagIntArray; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.network.play.server.SPacketSetPassengers; import net.minecraft.potion.PotionEffect; import net.minecraft.stats.StatList; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.EnumDifficulty; class PlayerCapabilities implements IPlayer { @@ -47,7 +47,7 @@ class PlayerCapabilities implements IPlayer { private static final DataParameter EFFECT = EntityDataManager .createKey(EntityPlayer.class, DataSerializers.COMPOUND_TAG); - private final List pages = Lists.newArrayList(); + private final Map pageStates = Maps.newHashMap(); private final PlayerAbilityDelegate powers = new PlayerAbilityDelegate(this); @@ -289,15 +289,27 @@ class PlayerCapabilities implements IPlayer { compound.setTag("powers", powers.toNBT()); compound.setTag("gravity", gravity.toNBT()); - if (hasUnlockedPages()) { - compound.setTag("pages", new NBTTagIntArray(pages)); - } - IMagicEffect effect = getEffect(); if (effect != null) { compound.setTag("effect", SpellRegistry.instance().serializeEffectToNBT(effect)); } + + if (!pageStates.isEmpty()) { + NBTTagCompound pages = new NBTTagCompound(); + boolean written = false; + + for (Map.Entry entry : pageStates.entrySet()) { + if (entry.getValue() != PageState.LOCKED) { + pages.setString(entry.getKey().toString(), entry.getValue().name()); + written = true; + } + } + + if (written) { + compound.setTag("pageStates", pages); + } + } } @Override @@ -311,17 +323,22 @@ class PlayerCapabilities implements IPlayer { setEffect(SpellRegistry.instance().createEffectFromNBT(compound.getCompoundTag("effect"))); } - if (compound.hasKey("pages")) { - pages.clear(); - for (int i : compound.getIntArray("pages")) { - pages.add(i); + pageStates.clear(); + if (compound.hasKey("pageStates")) { + NBTTagCompound pages = compound.getCompoundTag("pageStates"); + + for (String key : pages.getKeySet()) { + PageState state = PageState.of(pages.getString(key)); + + if (state != PageState.LOCKED) { + pageStates.put(new ResourceLocation(key), state); + } } } } @Override public void copyFrom(IPlayer oldPlayer) { - pages.addAll(oldPlayer.getUnlockedPages()); setEffect(oldPlayer.getEffect()); setPlayerSpecies(oldPlayer.getPlayerSpecies()); } @@ -363,8 +380,8 @@ class PlayerCapabilities implements IPlayer { public void setCurrentLevel(int level) { } - @Nonnull - public List getUnlockedPages() { - return pages; + @Override + public Map getPageStates() { + return pageStates; } } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellAffinity.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellAffinity.java index 6843326d..e8e3dda5 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/SpellAffinity.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellAffinity.java @@ -47,4 +47,13 @@ public enum SpellAffinity { return implications; } + + public static SpellAffinity of(String s) { + try { + if (s != null) + return valueOf(s.toUpperCase()); + } catch (Throwable e) {} + + return SpellAffinity.NEUTRAL; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/util/crafting/CraftingManager.java b/src/main/java/com/minelittlepony/unicopia/util/crafting/CraftingManager.java index 716f612f..0b232a83 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/crafting/CraftingManager.java +++ b/src/main/java/com/minelittlepony/unicopia/util/crafting/CraftingManager.java @@ -1,33 +1,16 @@ package com.minelittlepony.unicopia.util.crafting; -import java.io.BufferedReader; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Iterator; import java.util.Map; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.commons.io.FilenameUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import com.google.common.collect.Maps; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; +import com.minelittlepony.util.AssetWalker; import net.minecraft.inventory.InventoryCrafting; import net.minecraft.item.ItemStack; @@ -41,30 +24,30 @@ import net.minecraft.world.World; public class CraftingManager { - private static final Logger LOGGER = LogManager.getLogger(); - private final Map REGISTRY = Maps.newHashMap(); private final Map> JSON_PARSERS = Maps.newHashMap(); - private static final Gson gson = new GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .create(); - @Nonnull private final ResourceLocation crafting_id; + private final AssetWalker assets; + public CraftingManager(String modid, String resourcename) { this(new ResourceLocation(modid, resourcename + "/recipes")); } public CraftingManager(@Nonnull ResourceLocation id) { crafting_id = id; + assets = new AssetWalker(id, this::handleJson); load(); } + protected void handleJson(ResourceLocation id, JsonObject json) throws JsonParseException { + REGISTRY.put(id, parseRecipeJson(json)); + } + protected void registerRecipeTypes(Map> types) { types.put("crafting_shaped", ShapedRecipes::deserialize); types.put("crafting_shapeless", ShapelessRecipes::deserialize); @@ -76,62 +59,7 @@ public class CraftingManager { registerRecipeTypes(JSON_PARSERS); - try { - String loadLocation = "/assets/" + crafting_id.getNamespace() + "/" + crafting_id.getPath(); - - URL url = CraftingManager.class.getResource(loadLocation); - - if (url == null) { - LOGGER.error("Couldn't find .mcassetsroot"); - return; - } - - URI uri = url.toURI(); - - if ("file".equals(uri.getScheme())) { - loadRecipesFrom(Paths.get(CraftingManager.class.getResource(loadLocation).toURI())); - } else { - if (!"jar".equals(uri.getScheme())) { - LOGGER.error("Unsupported scheme " + uri + " trying to list all recipes"); - - return; - } - - try (FileSystem filesystem = FileSystems.newFileSystem(uri, Collections.emptyMap())) { - loadRecipesFrom(filesystem.getPath(loadLocation)); - } - } - } catch (IOException | URISyntaxException e) { - LOGGER.error("Couldn't get a list of all recipe files", e); - } - } - - private void loadRecipesFrom(@Nullable Path path) throws IOException { - if (path == null) { - return; - } - - Iterator iterator = Files.walk(path).iterator(); - - while (iterator.hasNext()) { - Path i = iterator.next(); - - if ("json".equals(FilenameUtils.getExtension(i.toString()))) { - ResourceLocation id = new ResourceLocation(FilenameUtils.removeExtension(path.relativize(i).toString()).replaceAll("\\\\", "/")); - - try(BufferedReader bufferedreader = Files.newBufferedReader(i)) { - REGISTRY.put(id, parseRecipeJson(JsonUtils.fromJson(gson, bufferedreader, JsonObject.class))); - } catch (JsonParseException e) { - LOGGER.error("Parsing error loading recipe " + id, e); - - return; - } catch (IOException e) { - LOGGER.error("Couldn't read recipe " + id + " from " + i, e); - - return; - } - } - } + assets.walk(); } protected IRecipe parseRecipeJson(JsonObject json) { diff --git a/src/main/java/com/minelittlepony/util/AssetWalker.java b/src/main/java/com/minelittlepony/util/AssetWalker.java new file mode 100644 index 00000000..40611323 --- /dev/null +++ b/src/main/java/com/minelittlepony/util/AssetWalker.java @@ -0,0 +1,110 @@ +package com.minelittlepony.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Iterator; + +import javax.annotation.Nullable; + +import org.apache.commons.io.FilenameUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.minelittlepony.unicopia.util.crafting.CraftingManager; + +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; + +public class AssetWalker { + private static final Logger LOGGER = LogManager.getLogger(); + + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .create(); + + private final String loadLocation; + + private final JsonConsumer consumer; + + public AssetWalker(ResourceLocation assetLocation, JsonConsumer consumer) { + this.consumer = consumer; + + loadLocation = "/assets/" + assetLocation.getNamespace() + "/" + assetLocation.getPath(); + } + + public void walk() { + try { + URL url = AssetWalker.class.getResource(loadLocation); + + if (url == null) { + LOGGER.error("Couldn't find .mcassetsroot"); + return; + } + + URI uri = url.toURI(); + + if ("file".equals(uri.getScheme())) { + readFiles(Paths.get(CraftingManager.class.getResource(loadLocation).toURI())); + } else { + if (!"jar".equals(uri.getScheme())) { + LOGGER.error("Unsupported scheme " + uri + " trying to list all recipes"); + + return; + } + + try (FileSystem filesystem = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + readFiles(filesystem.getPath(loadLocation)); + } + } + } catch (IOException | URISyntaxException e) { + LOGGER.error("Couldn't get a list of all json files", e); + } + } + + private void readFiles(@Nullable Path path) throws IOException { + if (path == null) { + return; + } + + Iterator iterator = Files.walk(path).iterator(); + + while (iterator.hasNext()) { + Path i = iterator.next(); + + if ("json".equals(FilenameUtils.getExtension(i.toString()))) { + ResourceLocation id = new ResourceLocation(FilenameUtils.removeExtension(path.relativize(i).toString()).replaceAll("\\\\", "/")); + + try(BufferedReader bufferedreader = Files.newBufferedReader(i)) { + consumer.accept(id, JsonUtils.fromJson(GSON, bufferedreader, JsonObject.class)); + } catch (JsonParseException e) { + LOGGER.error("Parsing error loading recipe " + id, e); + + return; + } catch (IOException e) { + LOGGER.error("Couldn't read recipe " + id + " from " + i, e); + + return; + } + } + } + } + + @FunctionalInterface + public interface JsonConsumer { + void accept(ResourceLocation id, JsonObject json) throws JsonParseException; + } +} diff --git a/src/main/resources/assets/unicopia/pages/preface.json b/src/main/resources/assets/unicopia/pages/preface.json new file mode 100644 index 00000000..8d8509ea --- /dev/null +++ b/src/main/resources/assets/unicopia/pages/preface.json @@ -0,0 +1,8 @@ +{ + "texture": "unicopia:preface", + "state": "unread", + "conditions": { + "type": "unicopia:compound_condition", + "conditions": [] + } +} diff --git a/src/main/resources/assets/unicopia/textures/pages/preface.png b/src/main/resources/assets/unicopia/textures/pages/preface.png new file mode 100644 index 00000000..606ad81a Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/pages/preface.png differ