/** Copyright (C) <2015> <coolAlias> This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such, you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package zeldaswordskills.entity.player.quests; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityAgeable; import net.minecraft.entity.passive.EntityVillager; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.IChatComponent; import zeldaswordskills.ZSSAchievements; import zeldaswordskills.entity.ZSSVillagerInfo.EnumVillager; import zeldaswordskills.entity.npc.EntityGoron; import zeldaswordskills.item.ItemTreasure; import zeldaswordskills.item.ItemTreasure.Treasures; import zeldaswordskills.item.ZSSItems; import zeldaswordskills.ref.Sounds; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.TimedAddItem; import zeldaswordskills.util.TimedChatDialogue; import zeldaswordskills.util.VillagerDescription; import com.google.common.collect.ImmutableSet; public final class QuestBiggoronSword extends QuestBase { /** Set of all trades in order of required completion */ private static final ImmutableSet<TradeData> TRADES = new ImmutableSet.Builder<TradeData>() .add(new TradeData(Treasures.TENTACLE, new VillagerDescription("Talon", EntityVillager.class, EnumVillager.FARMER.ordinal(), true), getTreasure(Treasures.POCKET_EGG))) .add(new TradeData(Treasures.POCKET_EGG, new VillagerDescription("Cucco Lady", EntityVillager.class, EnumVillager.FARMER.ordinal()), getTreasure(Treasures.COJIRO))) .add(new TradeData(Treasures.COJIRO, new VillagerDescription("Grog", EntityVillager.class, EnumVillager.BUTCHER.ordinal()), getTreasure(Treasures.ODD_MUSHROOM))) .add(new TradeData(Treasures.ODD_MUSHROOM, new VillagerDescription("Old Hag", EntityVillager.class, EnumVillager.LIBRARIAN.ordinal()), getTreasure(Treasures.ODD_POTION))) .add(new TradeData(Treasures.ODD_POTION, new VillagerDescription("Grog", EntityVillager.class, EnumVillager.BUTCHER.ordinal()), getTreasure(Treasures.POACHER_SAW))) .add(new TradeData(Treasures.POACHER_SAW, new VillagerDescription("Mutoh", EntityVillager.class, EnumVillager.BLACKSMITH.ordinal()), getTreasure(Treasures.GORON_SWORD))) .add(new TradeData(Treasures.GORON_SWORD, new VillagerDescription("Biggoron", EntityGoron.class, EnumVillager.BLACKSMITH.ordinal()), getTreasure(Treasures.PRESCRIPTION))) .add(new TradeData(Treasures.PRESCRIPTION, new VillagerDescription("King Zora", EntityVillager.class, EnumVillager.PRIEST.ordinal()), getTreasure(Treasures.EYEBALL_FROG))) .add(new TradeData(Treasures.EYEBALL_FROG, new VillagerDescription("Lake Scientist", EntityVillager.class, EnumVillager.LIBRARIAN.ordinal()), getTreasure(Treasures.EYE_DROPS))) .add(new TradeData(Treasures.EYE_DROPS, new VillagerDescription("Biggoron", EntityGoron.class), getTreasure(Treasures.CLAIM_CHECK))) .add(new TradeData(Treasures.CLAIM_CHECK, new VillagerDescription("Biggoron", EntityGoron.class), new ItemStack(ZSSItems.swordBiggoron))) .build(); private static final ItemStack getTreasure(Treasures treasure) { return new ItemStack(ZSSItems.treasure, 1, treasure.ordinal()); } /** Index of current trade requiring completion */ private int tradeIndex; private TradeData getCurrentTrade() { return (tradeIndex < TRADES.size() ? TRADES.asList().get(tradeIndex) : null); } public QuestBiggoronSword() { set(FLAG_BEGIN); // automatically begins } @Override protected boolean onBegin(EntityPlayer player, Object... data) { return false; // never called anyway } @Override public boolean canComplete(EntityPlayer player) { return false; // completed from #update } @Override protected boolean onComplete(EntityPlayer player, Object... data) { forceComplete(player, data); return false; } @Override public void forceComplete(EntityPlayer player, Object... data) { player.triggerAchievement(ZSSAchievements.treasureBiggoron); tradeIndex = TRADES.size(); set(FLAG_COMPLETE); } /** * @param data[0] must be the entity with which the player is interacting * @param data[1] should be a Boolean value: true if this is a left-click interaction */ @Override public boolean update(EntityPlayer player, Object... data) { if (data == null || data.length < 2 || !(data[0] instanceof Entity) || !(data[1] instanceof Boolean)) { return false; } ItemStack stack = player.getHeldItem(); if (stack == null || !(stack.getItem() instanceof ItemTreasure)) { return false; } TradeData trade = getCurrentTrade(); if (trade != null && trade.villager.matches((Entity) data[0]) && trade.treasure == Treasures.byDamage(stack.getItemDamage())) { if ((Boolean) data[1]) { // left click interaction if (trade.treasure != Treasures.CLAIM_CHECK || checkClaim(stack, player)) { if (!PlayerUtils.consumeHeldItem(player, stack.getItem(), stack.getItemDamage(), 1)) { return false; } rewardAndChat(player, trade, true); if (tradeIndex < TRADES.size()) { ++tradeIndex; } return true; } else if (trade.treasure == Treasures.CLAIM_CHECK) { return true; // #checkClaim would have sent a chat, so cancel interaction } } else { // right click interaction if (trade.treasure != Treasures.CLAIM_CHECK || checkClaim(stack, player)) { PlayerUtils.sendTranslatedChat(player, "chat.zss.treasure." + trade.treasure.name + ".show"); } return true; } } // uninterested chat handled by ItemTreasure click methods return false; } private void rewardAndChat(EntityPlayer player, TradeData trade, boolean first) { ItemStack reward = trade.reward.copy(); List<IChatComponent> chat = new ArrayList<IChatComponent>(); if (first) { chat.add(new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".give.first")); } else if (trade.treasure == Treasures.CLAIM_CHECK) { player.addChatMessage(new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".give.repeat")); return; // CLAIM_CHECK reward can only be received once - additional swords must be purchased } else { chat.add(new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".give.repeat")); } chat.add(new ChatComponentTranslation("chat.zss.treasure.generic.received", new ChatComponentTranslation(reward.getUnlocalizedName() + ".name"))); if (onCompletedTrade(player, trade, reward) && first) { chat.add(new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".give.next")); } new TimedChatDialogue(player, chat.toArray(new IChatComponent[chat.size()])); new TimedAddItem(player, reward, 1250, Sounds.SUCCESS); } /** * Call for CLAIM_CHECK only - returns true if 2+ days have passed; if false, may * have sent some dialogue. Will create / initialize stack NBT tag if missing. */ private boolean checkClaim(ItemStack stack, EntityPlayer player) { if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } if (!stack.getTagCompound().hasKey("finishDate")) { PlayerUtils.sendTranslatedChat(player, "chat.zss.treasure.claim_check.no_nbt"); stack.getTagCompound().setLong("finishDate", player.worldObj.getTotalWorldTime() + 48000); return false; } else if (player.worldObj.getTotalWorldTime() < stack.getTagCompound().getLong("finishDate")) { PlayerUtils.sendTranslatedChat(player, "chat.zss.treasure.claim_check.early"); return false; } return true; } /** * Returns true if 'next' portion of the chat dialogue should be added now */ private boolean onCompletedTrade(EntityPlayer player, TradeData trade, ItemStack reward) { switch (trade.treasure) { case TENTACLE: player.triggerAchievement(ZSSAchievements.treasureFirst); return false; case POCKET_EGG: player.triggerAchievement(ZSSAchievements.treasureSecond); return false; case EYE_DROPS: // traded eye drops for claim check: set date for claiming finished sword reward.setTagCompound(new NBTTagCompound()); reward.getTagCompound().setLong("finishDate", player.worldObj.getTotalWorldTime() + 48000); return true; case CLAIM_CHECK: onComplete(player); return true; default: return true; } } /** * Called from ItemTreasure's left- and right-click methods * @param data[0] must be the entity with which the player is interacting * @param data[1] should be a Boolean value: true if this is a left-click interaction */ @Override public IChatComponent getHint(EntityPlayer player, Object... data) { if (data == null || data.length < 1 || !(data[0] instanceof Entity)) { return null; } Entity entity = (Entity) data[0]; boolean isChild = (entity instanceof EntityAgeable && ((EntityAgeable) entity).isChild()); // set later as a flag Treasures treasure = null; ItemStack stack = player.getHeldItem(); if (stack != null && stack.getItem() instanceof ItemTreasure) { treasure = Treasures.byDamage(stack.getItemDamage()); } boolean isLeftClick = (data.length > 1 && data[1] instanceof Boolean && (Boolean) data[1]); IChatComponent chat = null; // index is 1 greater than normal to compensate for reverse traversal, +1 more to also check next trade entry int index = Math.min(tradeIndex + 2, TRADES.size()); ListIterator<TradeData> iterator = TRADES.asList().listIterator(index); for (int i = index - 1; iterator.hasPrevious(); --i) { // subtract 1 to compensate for call to #previous TradeData trade = iterator.previous(); // Only allow 'ageable' variants for children, i.e. "Talon" if (trade.villager.matches(entity, false) && (!isChild || trade.villager.isChild)) { boolean exactMatch = trade.villager.matchChild(entity); if (i + 1 == tradeIndex && trade.reward.getItem() instanceof ItemTreasure && treasure == Treasures.byDamage(trade.reward.getItemDamage())) { // matched entity from previous stage while holding the reward from that stage - gives hint for 'next' (i.e. current) stage return new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".hint.next" + (exactMatch ? "" : ".ageable")); } else if (i == tradeIndex && (trade.treasure != treasure || !exactMatch)) { // matched entity from current stage, but treasure or isChild mismatch - give hint return new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".hint.current" + (exactMatch ? "" : ".ageable")); } else if (i - 1 == tradeIndex && trade.treasure != treasure && !isComplete(player)) { // matched entity from next stage; give either a generic or specific hint, depending on held item return new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".hint.previous" + (treasure == iterator.previous().treasure ? ".match" : "")); } else if (i < tradeIndex) { // matched entity from completed stage, any treasure item; check for repeat or say 'thanks' if (isLeftClick && trade.treasure == treasure && exactMatch) { // player trying to repeat trade if (PlayerUtils.consumeHeldItem(player, stack.getItem(), stack.getItemDamage(), 1)) { rewardAndChat(player, trade, false); return new ChatComponentTranslation(""); // empty chat to prevent further interactions } } return new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + ".thanks" + (exactMatch ? "" : ".ageable")); } return null; // always terminate loop once an exact match is found } else if (trade.treasure == treasure && trade.villager.matchClassAndName(entity) && !trade.villager.matchProfession(entity)) { // everything matches but profession - help the player out with a hint EnumVillager ev = EnumVillager.values()[trade.villager.profession % EnumVillager.values().length]; return new ChatComponentTranslation("chat.zss.treasure.generic.profession." + (isChild ? "child" : ev.unlocalizedName), new ChatComponentTranslation("entity.villager.profession." + ev.unlocalizedName)); } else if (trade.treasure == treasure && entity instanceof EntityVillager && checkVillagerTrade(stack, player, (EntityVillager) entity, isLeftClick)) { return new ChatComponentTranslation(""); // empty chat to prevent further action (e.g. to stop villager trading GUI from opening) } else if (trade.treasure == treasure && !isChild) { // children are handled in ItemTreasure // hint when interacting with random villagers, at most once per loop if (rand.nextInt(8) < 3) { chat = new ChatComponentTranslation("chat.zss.treasure." + trade.treasure.name + (isLeftClick ? ".give" : ".show") + ".random"); } else { chat = new ChatComponentTranslation("chat.zss.treasure.generic." + (isLeftClick ? "give." : "show.") + rand.nextInt(4)); } } } return chat; } private boolean checkVillagerTrade(ItemStack stack, EntityPlayer player, EntityVillager villager, boolean isLeftClick) { Treasures treasure = Treasures.byDamage(stack.getItemDamage()); return treasure.canSell() && ((ItemTreasure) stack.getItem()).handleVillagerTrade(stack, player, villager, isLeftClick); } @Override public void writeToNBT(NBTTagCompound compound) { super.writeToNBT(compound); compound.setInteger("tradeIndex", tradeIndex); } @Override public void readFromNBT(NBTTagCompound compound) { super.readFromNBT(compound); tradeIndex = compound.getInteger("tradeIndex"); } private static class TradeData { /** Treasure needed to complete this step */ public final Treasures treasure; /** Description of villager interested in this trade */ public final VillagerDescription villager; /** Reward for completing this step */ public final ItemStack reward; public TradeData(Treasures treasure, VillagerDescription villager, ItemStack reward) { this.treasure = treasure; this.villager = villager; this.reward = reward; } } }