/** 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; import java.util.EnumMap; import java.util.Map; import net.minecraft.entity.Entity; import net.minecraft.entity.passive.EntityVillager; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.BlockPos; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.village.MerchantRecipe; import net.minecraft.village.Village; import net.minecraft.world.World; import net.minecraftforge.common.IExtendedEntityProperties; import zeldaswordskills.entity.mobs.EntityChu.ChuType; import zeldaswordskills.entity.player.ZSSPlayerInfo; import zeldaswordskills.entity.player.ZSSPlayerSkills; import zeldaswordskills.entity.player.quests.QuestMaskSales; import zeldaswordskills.item.ItemBombBag; import zeldaswordskills.item.ItemTreasure.Treasures; import zeldaswordskills.item.ZSSItems; import zeldaswordskills.ref.Config; import zeldaswordskills.ref.Sounds; import zeldaswordskills.skills.SkillBase; import zeldaswordskills.util.BossType; import zeldaswordskills.util.MerchantRecipeHelper; import zeldaswordskills.util.PlayerUtils; import zeldaswordskills.util.TimedAddItem; import zeldaswordskills.util.TimedChatDialogue; /** * * Additional data for handling extended villager trading system. * */ public class ZSSVillagerInfo implements IExtendedEntityProperties { /** Define villager types for easier to read code */ public enum EnumVillager { FARMER("farmer"), LIBRARIAN("librarian"), PRIEST("priest"), BLACKSMITH("blacksmith"), BUTCHER("butcher"); public final String unlocalizedName; private EnumVillager(String name) { this.unlocalizedName = name; } } private static final String SAVE_KEY = "zssVillagerInfo"; /** Used for masks when the villager is not interested */ private static final int NONE = -1; /** The villager to which these properties belong */ private final EntityVillager villager; /** Additional save data for villager trading */ private NBTTagCompound data; /** The index of the mask that this villager wants, or NONE; initial value is masks.size() as a flag */ private int desiredMask; /** Trade mapping for a single Treasure enum type key to the full trade recipe output */ private static final Map<Treasures, MerchantRecipe> treasureTrades = new EnumMap<Treasures, MerchantRecipe>(Treasures.class); /** Maps the villager profession capable of trading for the treasure trade to the Treasure enum type key */ private static final Map<Treasures, Integer> treasureVillager = new EnumMap<Treasures, Integer>(Treasures.class); /** Temporarily stores nearby village when needed for mating */ private Village village; /** Temporarily stores this villager's current mate */ private EntityVillager mate = null; /** Temporarily stores the time remaining before a child will be born */ private int matingTime = 0; public ZSSVillagerInfo(EntityVillager villager) { this.villager = villager; data = new NBTTagCompound(); desiredMask = QuestMaskSales.MASKS.size(); } public static final void register(EntityVillager villager) { villager.registerExtendedProperties(SAVE_KEY, new ZSSVillagerInfo(villager)); } public static final ZSSVillagerInfo get(EntityVillager villager) { return (ZSSVillagerInfo) villager.getExtendedProperties(SAVE_KEY); } /** * Returns the mask that this villager desires, or null if none */ public Item getMaskDesired() { if (desiredMask == QuestMaskSales.MASKS.size()) { if (villager.worldObj.rand.nextFloat() < Config.getMaskBuyChance()) { desiredMask = villager.worldObj.rand.nextInt(QuestMaskSales.MASKS.size()); } else { desiredMask = NONE; } } return (desiredMask != NONE ? QuestMaskSales.getMask(desiredMask) : null); } public void handleSkulltulaTrade(ItemStack stack, EntityPlayer player) { ZSSPlayerInfo info = ZSSPlayerInfo.get(player); String s = "chat.zss.npc.cursed_man."; if (villager.isChild()) { PlayerUtils.sendTranslatedChat(player, s + "child"); } else if (info.canIncrementSkulltulaTokens()) { if (info.incrementSkulltulaTokens()) { int n = info.getSkulltulaTokens(); ItemStack reward = null; switch (n) { case 1: PlayerUtils.sendTranslatedChat(player, s + "token." + n); break; case 10: reward = new ItemStack(ZSSItems.whip); break; case 20: reward = new ItemStack(ZSSItems.tunicZoraChest); break; case 30: reward = new ItemStack(ZSSItems.bombBag); ((ItemBombBag) reward.getItem()).addBombs(reward, new ItemStack(ZSSItems.bomb, 10)); break; case 40: reward = new ItemStack(ZSSItems.keyBig, 1, player.worldObj.rand.nextInt(BossType.values().length)); break; case 50: ZSSPlayerSkills skills = ZSSPlayerSkills.get(player); for (SkillBase skill : SkillBase.getSkills()) { if (skill.getId() != SkillBase.bonusHeart.getId() && skills.getSkillLevel(skill) < skill.getMaxLevel() && (reward == null || player.worldObj.rand.nextInt(4) == 0)) { reward = new ItemStack(ZSSItems.skillOrb, 1, skill.getId()); } } if (reward == null) { reward = new ItemStack(ZSSItems.arrowLight, 16); } break; case 100: reward = new ItemStack(Items.emerald, 64); if (Config.getSkulltulaRewardRate() > 0) { villager.getEntityData().setLong("NextSkulltulaReward", player.worldObj.getTotalWorldTime() + (24000 * Config.getSkulltulaRewardRate())); } break; default: PlayerUtils.sendTranslatedChat(player, s + "amount", n); } if (reward != null) { new TimedChatDialogue(player, new ChatComponentTranslation(s + "token." + n), new ChatComponentTranslation(s + "reward." + n, new ChatComponentTranslation(reward.getUnlocalizedName() + ".name"))); new TimedAddItem(player, reward, 2500, Sounds.SUCCESS); } } else { // probably an impossible case PlayerUtils.sendTranslatedChat(player, s + villager.worldObj.rand.nextInt(4)); } } else if (Config.getSkulltulaRewardRate() > 0 && player.worldObj.getTotalWorldTime() > villager.getEntityData().getLong("NextSkulltulaReward")) { ItemStack reward = new ItemStack(Items.emerald, 64); new TimedChatDialogue(player, new ChatComponentTranslation(s + "complete"), new ChatComponentTranslation(s + "reward.100", new ChatComponentTranslation(reward.getUnlocalizedName() + ".name"))); new TimedAddItem(player, reward, 2500, Sounds.SUCCESS); villager.getEntityData().setLong("NextSkulltulaReward", player.worldObj.getTotalWorldTime() + (24000 * Config.getSkulltulaRewardRate())); } else { PlayerUtils.sendTranslatedChat(player, s + "complete"); } } /** Returns true if this villager is any type of Hunter */ public boolean isHunter() { return !villager.isChild() && villager.getProfession() == EnumVillager.BUTCHER.ordinal() && villager.getCustomNameTag() != null && villager.getCustomNameTag().contains("Hunter"); } /** Returns true if this villager is a Monster Hunter */ public boolean isMonsterHunter() { return isHunter() && villager.getCustomNameTag().equals("Monster Hunter"); } /** * Adds any item to this hunter's list of things to buy (does nothing if {@link #isHunter()} is false) * @param toBuy ItemStack that the hunter will purchase * @param price Base price the hunter should pay for the item in question */ public void addHunterTrade(EntityPlayer player, ItemStack toBuy, int price) { if (!isHunter()) { return; } price = isMonsterHunter() ? price + price / 2 : price; if (MerchantRecipeHelper.addToListWithCheck(villager.getRecipes(player), new MerchantRecipe(toBuy, new ItemStack(Items.emerald, price)))) { PlayerUtils.playSound(player, Sounds.SUCCESS, 1.0F, 1.0F); PlayerUtils.sendTranslatedChat(player, "chat.zss.treasure.hunter.new", new ChatComponentTranslation(toBuy.getUnlocalizedName() + ".name")); } else { PlayerUtils.sendTranslatedChat(player, "chat.zss.treasure.hunter.old", new ChatComponentTranslation(toBuy.getUnlocalizedName() + ".name")); } } /** * Returns what the villager is willing to trade for the treasure, if anything. * Note that the first item to buy is always a stack containing one of the treasure. * @param treasure the treasure to be traded * @return may be null */ public MerchantRecipe getTreasureTrade(Treasures treasure) { if (treasureTrades.containsKey(treasure) && treasureVillager.get(treasure) == villager.getProfession()) { return treasureTrades.get(treasure); } else if (isHunter() && treasure.canSell()) { boolean flag = ("Monster Hunter").equals(villager.getCustomNameTag()); int price = treasure.getValue(); price = flag ? price + (price / 2) : price; return new MerchantRecipe(new ItemStack(ZSSItems.treasure, 1, treasure.ordinal()), new ItemStack(Items.emerald, price)); } return null; } /** * Returns true if the villager is interested in the specified treasure * @param stack the player's currently held item, which should be the treasure item */ public boolean isInterested(Treasures treasure, ItemStack stack) { return treasureTrades.containsKey(treasure) && treasureVillager.get(treasure) == villager.getProfession(); } /** Adds the given amount of chu jellies to the current amount */ public void addJelly(ChuType type, int amount) { data.setInteger(("jelliesReceived" + type.ordinal()), getJelliesReceived(type) + amount); } /** Returns the number of jellies of this type that the villager has received */ public int getJelliesReceived(ChuType type) { return (data.hasKey("jelliesReceived" + type.ordinal()) ? data.getInteger("jelliesReceived" + type.ordinal()) : 0); } /** Returns whether this villager deals at all in Chu Jellies */ public boolean isChuTrader() { return !villager.isChild() && villager.getProfession() == EnumVillager.LIBRARIAN.ordinal() && villager.getCustomNameTag().contains("Doc"); } /** * Checks that this villager has received enough jellies to trade; if not, attempts * to fulfill the quota from the stack provided * @return true if the quota was met */ public boolean canSellType(ChuType type, ItemStack stack) { int jellies = getJelliesReceived(type); while (jellies < 15 && stack.stackSize > 0) { --stack.stackSize; ++jellies; } data.setInteger(("jelliesReceived" + type.ordinal()), jellies); return jellies >= 15; } /** True if the villager is currently mating */ private boolean isMating() { return matingTime > 0 && villager.getGrowingAge() == 0 && areSufficientDoors(); } /** * Sets this villager into forced mating mode if possible */ public void setMating() { if (villager.getGrowingAge() == 0 && !isMating() && villager.worldObj.getVillageCollection() != null) { village = villager.worldObj.getVillageCollection().getNearestVillage(new BlockPos(villager), 0); if (areSufficientDoors()) { Entity e = villager.worldObj.findNearestEntityWithinAABB(EntityVillager.class, villager.getEntityBoundingBox().expand(8.0D, 3.0D, 8.0D), villager); if (e != null && ((EntityVillager) e).getGrowingAge() == 0) { mate = (EntityVillager) e; matingTime = 300; villager.setMating(true); } } } } private void updateMating() { if (isMating()) { --matingTime; villager.getLookHelper().setLookPositionWithEntity(mate, 10.0F, 30.0F); if (villager.getDistanceSqToEntity(mate) > 2.25D) { villager.getNavigator().tryMoveToEntityLiving(mate, 0.25D); } else if (matingTime == 0 && mate.isMating()) { giveBirth(); resetMating(); } if (villager.getRNG().nextInt(35) == 0) { villager.worldObj.setEntityState(villager, (byte) 12); } } } /** Returns true if there are sufficient doors, up to one villager per door (3x vanilla size) */ private boolean areSufficientDoors() { return village != null ? (village.getNumVillagers() < village.getNumVillageDoors()) : false; } /** Resets the mating fields */ private void resetMating() { villager.setMating(false); mate = null; } private void giveBirth() { EntityVillager baby = (EntityVillager) villager.createChild(mate); mate.setGrowingAge(6000); villager.setGrowingAge(6000); baby.setGrowingAge(-24000); baby.setLocationAndAngles(villager.posX, villager.posY, villager.posZ, 0.0F, 0.0F); villager.worldObj.spawnEntityInWorld(baby); villager.worldObj.setEntityState(baby, (byte) 12); } /** * Call each update tick */ public void onUpdate() { updateMating(); } @Override public void init(Entity entity, World world) {} @Override public void saveNBTData(NBTTagCompound compound) { compound.setTag(SAVE_KEY, data); compound.setInteger("desiredMask", desiredMask); } @Override public void loadNBTData(NBTTagCompound compound) { data = (compound.hasKey(SAVE_KEY) ? compound.getCompoundTag(SAVE_KEY) : new NBTTagCompound()); desiredMask = compound.getInteger("desiredMask"); } /** * Adds every component involved in a treasure trade * @param treasure the treasure to be traded * @param villager the profession of the villager allowed to have this trade, or null for no requirement * @param required the itemstack required for the trade in addition to the Treasure * @param output the itemstack to be traded for */ private static final void addTreasureTrade(Treasures treasure, EnumVillager villager, ItemStack required, ItemStack output) { treasureVillager.put(treasure, (villager != null ? villager.ordinal() : null)); treasureTrades.put(treasure, new MerchantRecipe(new ItemStack(ZSSItems.treasure,1,treasure.ordinal()), required, output)); } /** * Initializes all custom trade maps for custom villager trades */ public static void initTrades() { addTreasureTrade(Treasures.EVIL_CRYSTAL,EnumVillager.PRIEST,new ItemStack(ZSSItems.arrowLight,16),new ItemStack(ZSSItems.crystalSpirit)); } }