/** 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.npc; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAIAttackOnCollide; import net.minecraft.entity.passive.EntityVillager; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import net.minecraft.util.MathHelper; import net.minecraft.village.Village; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; import net.minecraftforge.fml.common.eventhandler.Event.Result; import zeldaswordskills.api.damage.DamageUtils; import zeldaswordskills.api.entity.INpcVillager; import zeldaswordskills.api.entity.IParryModifier; import zeldaswordskills.entity.DirtyEntityAccessor; import zeldaswordskills.entity.player.ZSSPlayerSkills; import zeldaswordskills.item.ItemTreasure; import zeldaswordskills.item.ItemTreasure.Treasures; import zeldaswordskills.item.ZSSItems; import zeldaswordskills.ref.Sounds; import zeldaswordskills.skills.ICombo; import zeldaswordskills.skills.SkillBase; import zeldaswordskills.util.PlayerUtils; /** * * Orca will teach Link some special sword skills in exchange for Knight's Crests. * * Spawned by naming any villager 'Orca' and interacting while holding a Knight's Crest. * */ public class EntityNpcOrca extends EntityNpcBase implements INpcVillager, IParryModifier { /** Datawatcher index to track with whom Orca is in a match (so player interaction can be prevented on client) */ private static final int MATCH_PLAYER_ID = 18; /** Amount of time that must pass before Orca will spar again */ private static final int MATCH_INTERVAL = 3000; /** Time at which Orca will next be ready for a sparring match */ private long nextMatch; /** Timer to prevent chat from being spammed too often */ private int chatTimer; /** Counter to limit number of times combat can be extended; reset to 0 each time player attacks */ private int hitCounter; /** Set to true when player has parried a blow */ private boolean parryFlag; public EntityNpcOrca(World world) { super(world); tasks.addTask(2, new EntityAIAttackOnCollide(this, EntityPlayer.class, 0.6D, true)); } @Override protected void entityInit() { super.entityInit(); dataWatcher.addObject(MATCH_PLAYER_ID, 0); } @Override protected void applyEntityAttributes() { super.applyEntityAttributes(); getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(50.0D); } /** Whether Orca is currently in a match */ public Entity getMatchOpponent() { return worldObj.getEntityByID(dataWatcher.getWatchableObjectInt(MATCH_PLAYER_ID)); } private void setMatchOpponent(Entity entity) { if (entity instanceof EntityPlayer) { // TODO triggerAchievement "Sparring Partners" } dataWatcher.updateObject(MATCH_PLAYER_ID, (entity == null ? -1 : entity.getEntityId())); } @Override protected String getNameTagOnSpawn() { return "Orca"; } @Override protected String getLivingSound() { return Sounds.VILLAGER_HAGGLE; } @Override protected String getHurtSound() { return Sounds.VILLAGER_HIT; } @Override protected String getDeathSound() { return Sounds.VILLAGER_DEATH; } @Override public boolean isEntityInvulnerable(DamageSource source) { return false; // allow Orca to be attacked for training purposes } // Orca ignores fall damage so practicing Rising Cut doesn't kill him @Override public void fall(float distance, float damageMultiplier) {} @Override public boolean attackEntityFrom(DamageSource source, float damage) { if (source.getEntity() instanceof EntityPlayer) { EntityPlayer player = (EntityPlayer) source.getEntity(); Entity opponent = getMatchOpponent(); if (worldObj.getTotalWorldTime() < nextMatch) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.resting", true); return false; } else if (opponent != null && opponent != player) { sendTranslatedChat(player, "chat.zss.npc.orca.match.in_match", true); return false; } else if (source.getSourceOfDamage() != player && !source.getDamageType().equals(DamageUtils.INDIRECT_SWORD) && !source.getDamageType().equals(DamageUtils.INDIRECT_COMBO)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.melee_only", false); return false; } else if (player.getHealth() < 2.0F) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.low_health", true); return false; } else if (getHeldItem() == null) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.unarmed." + worldObj.rand.nextInt(3), true); setCurrentItemOrArmor(0, new ItemStack(Items.wooden_sword)); return false; // Damage source for armor break not set until damage actually inflicted, so check if skill is active instead } else if (!PlayerUtils.isHoldingWeapon(player)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.player_no_weapon", true); return false; } else if (ZSSPlayerSkills.get(player).isSkillActive(SkillBase.armorBreak)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.armor_break", false); } else if (ZSSPlayerSkills.get(player).isSkillActive(SkillBase.endingBlow)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.ending_blow", false); } else if (ZSSPlayerSkills.get(player).isSkillActive(SkillBase.risingCut)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.rising_cut", false); } else if (source.getDamageType().equals(DamageUtils.INDIRECT_SWORD) || source.getDamageType().equals(DamageUtils.INDIRECT_COMBO)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.ranged_sword", false); } else if (source.getDamageType().equals(DamageUtils.NON_SWORD)) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.non_sword", false); } else { ZSSPlayerSkills skills = ZSSPlayerSkills.get(player); if (skills.getTargetingSkill() == null) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.unskilled." + worldObj.rand.nextInt(3), false); } else if (skills.getTargetingSkill().getCurrentTarget() == this) { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.hit." + worldObj.rand.nextInt(4), false); } else { sendTranslatedChat(player, "chat.zss.npc.orca.match.damage.wrong_target", false); } } } return super.attackEntityFrom(source, damage); } @Override protected void damageEntity(DamageSource source, float amount) { if (source.getEntity() instanceof EntityPlayer) { // Post hurt event with proper damage amount to allow triggering of skills amount = DirtyEntityAccessor.getModifiedDamage(this, source, amount); net.minecraftforge.common.ForgeHooks.onLivingHurt(this, source, amount); hitCounter = 0; // reset consecutive hit counter /* Entity opponent = getMatchOpponent(); if (opponent == null || opponent != source.getEntity()) { hitCounter = 0; // reset hit counter for new opponent } */ } else { super.damageEntity(source, amount); } } @Override public boolean attackEntityAsMob(Entity entity) { if (entity instanceof EntityPlayer && ((EntityPlayer) entity).getHealth() < 2.0F) { setRevengeTarget(null); sendTranslatedChat((EntityPlayer) entity, "chat.zss.npc.orca.match.victory." + worldObj.rand.nextInt(4), true); } else if (entity.attackEntityFrom(DamageSource.causeMobDamage(this), 1F)) { if (entity instanceof EntityPlayer) { EntityPlayer player = (EntityPlayer) entity; boolean isBlocking = PlayerUtils.isBlocking(player); if (!isBlocking && ++hitCounter > 2) { // hit player 3 times, player loses setRevengeTarget(null); sendTranslatedChat(player, "chat.zss.npc.orca.match.victory." + worldObj.rand.nextInt(4), true); } else { setRevengeTarget(player); if (isBlocking) { sendTranslatedChat(player, "chat.zss.npc.orca.match.attack.blocked." + worldObj.rand.nextInt(2), false); } else { sendTranslatedChat(player, "chat.zss.npc.orca.match.attack." + worldObj.rand.nextInt(4), false); } } // End player combo each time a blow is landed and not blocked, regardless of damage: ICombo combo = ZSSPlayerSkills.get(player).getComboSkill(); if (!isBlocking && combo != null && combo.isComboInProgress()) { combo.getCombo().endCombo(player); } } int knockback = 0; if (entity instanceof EntityLivingBase) { knockback += EnchantmentHelper.getKnockbackModifier(this); if (knockback < 1 && entity instanceof EntityPlayer) { knockback = 1; } } if (knockback > 0) { float f = (float) knockback * 0.5F; double dx = -MathHelper.sin(rotationYaw * (float) Math.PI / 180.0F) * f; double dz = MathHelper.cos(rotationYaw * (float) Math.PI / 180.0F) * f; entity.addVelocity(dx, 0.1D, dz); motionX *= 0.6D; motionZ *= 0.6D; } return true; } else if (entity instanceof EntityPlayer && ZSSPlayerSkills.get((EntityPlayer) entity).isSkillActive(SkillBase.dodge)) { sendTranslatedChat((EntityPlayer) entity, "chat.zss.npc.orca.match.attack.dodged." + worldObj.rand.nextInt(2), false); } return false; } @Override public void setRevengeTarget(EntityLivingBase entity) { EntityLivingBase prevTarget = getAttackTarget(); Village tmp = villageObj; villageObj = null; // prevent player from losing village rep super.setRevengeTarget(entity); setAttackTarget(entity); // needed for attack AI to work attackingPlayer = (entity instanceof EntityPlayer ? (EntityPlayer) entity : null); setMatchOpponent(attackingPlayer); villageObj = tmp; if (entity == null && prevTarget instanceof EntityPlayer) { nextMatch = worldObj.getTotalWorldTime() + MATCH_INTERVAL; if ((ticksExisted - getRevengeTimer()) > 99) { // match timed out sendTranslatedChat((EntityPlayer) prevTarget, "chat.zss.npc.orca.match.timeout." + worldObj.rand.nextInt(3), true); } } } @Override public void onLivingUpdate() { updateArmSwingProgress(); if (chatTimer > 0) { --chatTimer; } if (getAttackTarget() instanceof EntityPlayer) { EntityPlayer player = (EntityPlayer) getAttackTarget(); ICombo combo = ZSSPlayerSkills.get(player).getComboSkill(); if (parryFlag) { parryFlag = false; if (getHeldItem() == null) { // TODO ? achievement for disarming Orca setRevengeTarget(null); sendTranslatedChat(player, "chat.zss.npc.orca.match.defeat.disarmed." + worldObj.rand.nextInt(3), true); } else { sendTranslatedChat(player, "chat.zss.npc.orca.match.disarm_attempt." + worldObj.rand.nextInt(3), false); } } else if (combo != null && combo.isComboInProgress() && combo.getCombo().getConsecutiveHits() > 9) { // TODO ? achievement for defeating Orca setRevengeTarget(null); sendTranslatedChat(player, "chat.zss.npc.orca.match.defeat.combo." + worldObj.rand.nextInt(3), true); } else if (getHeldItem() == null) { // weapon was probably destroyed via Sword Break // TODO ? achievement for disarming Orca setRevengeTarget(null); sendTranslatedChat(player, "chat.zss.npc.orca.match.defeat.disarmed." + worldObj.rand.nextInt(3), true); } else if (recentlyHit < 60 && !PlayerUtils.isHoldingWeapon(player)) { setRevengeTarget(null); sendTranslatedChat(player, "chat.zss.npc.orca.match.quit." + worldObj.rand.nextInt(4), true); } else if (recentlyHit < 10) { sendTranslatedChat(player, "chat.zss.npc.orca.match.player_idle." + worldObj.rand.nextInt(3), false); recentlyHit = 60; // reset counter so idle chat message doesn't get spammed } } super.onLivingUpdate(); } /** * Method to use for sending chats during combat to prevent player getting spammed with chat */ private void sendTranslatedChat(EntityPlayer player, String chat, boolean alwaysSend) { if (!worldObj.isRemote && (alwaysSend || rand.nextInt(Math.max(1, chatTimer)) == 0)) { PlayerUtils.sendTranslatedChat(player, chat); chatTimer = worldObj.rand.nextInt(20) + worldObj.rand.nextInt(20) + 20; } } @Override public boolean interact(EntityPlayer player) { Entity opponent = getMatchOpponent(); if (opponent != null) { if (opponent != player) { PlayerUtils.sendTranslatedChat(player, "chat.zss.npc.orca.match.in_match"); } return false; } if (!player.worldObj.isRemote) { ItemStack stack = player.getHeldItem(); if (ZSSPlayerSkills.get(player).completedCrests()) { PlayerUtils.sendTranslatedChat(player, "chat.zss.npc.orca.master." + player.worldObj.rand.nextInt(4)); } else if (stack != null && stack.getItem() instanceof ItemTreasure && stack.getItemDamage() == Treasures.KNIGHTS_CREST.ordinal()) { ZSSPlayerSkills.get(player).giveCrest(); } else { PlayerUtils.sendTranslatedChat(player, "chat.zss.npc.orca.idle." + rand.nextInt(5)); } } return true; } @Override public Result canInteractConvert(EntityPlayer player, EntityVillager villager) { return Result.DEFAULT; } @Override public Result canLeftClickConvert(EntityPlayer player, EntityVillager villager) { if (!villager.worldObj.isRemote && villager.getClass() == EntityVillager.class && !villager.isChild()) { ItemStack stack = player.getHeldItem(); return (stack != null && stack.getItem() == ZSSItems.treasure && Treasures.byDamage(stack.getItemDamage()) == Treasures.KNIGHTS_CREST) ? Result.ALLOW : Result.DEFAULT; } return Result.DEFAULT; } @Override public void onConverted(EntityPlayer player) { PlayerUtils.playSound(player, Sounds.SUCCESS, 1.0F, 1.0F); ZSSPlayerSkills.get(player).giveCrest(); } @Override public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData data) { data = super.onInitialSpawn(difficulty, data); setCurrentItemOrArmor(0, new ItemStack(Items.wooden_sword)); return data; } @Override public float getOffensiveModifier(EntityLivingBase entity, ItemStack stack) { parryFlag = true; return 0.5F; // impossible to disarm unless player has bonuses other than from Parry skill level } @Override public float getDefensiveModifier(EntityLivingBase entity, ItemStack stack) { return 0; } @Override public void writeEntityToNBT(NBTTagCompound compound) { super.writeEntityToNBT(compound); compound.setLong("nextMatch", nextMatch); } @Override public void readEntityFromNBT(NBTTagCompound compound) { super.readEntityFromNBT(compound); nextMatch = compound.getLong("nextMatch"); } }