/**
Copyright (C) <2017> <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.client;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.model.IBakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.entity.Entity;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.Potion;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.ClientProxy;
import zeldaswordskills.ZSSMain;
import zeldaswordskills.api.item.ArmorIndex;
import zeldaswordskills.api.item.ISwingSpeed;
import zeldaswordskills.api.item.IZoom;
import zeldaswordskills.api.item.IZoomHelper;
import zeldaswordskills.client.gui.ComboOverlay;
import zeldaswordskills.client.gui.GuiBuffBar;
import zeldaswordskills.client.gui.GuiEndingBlowOverlay;
import zeldaswordskills.client.gui.GuiItemModeOverlay;
import zeldaswordskills.client.gui.GuiMagicMeter;
import zeldaswordskills.client.gui.GuiMagicMeterText;
import zeldaswordskills.client.gui.IGuiOverlay;
import zeldaswordskills.entity.ZSSEntityInfo;
import zeldaswordskills.entity.buff.Buff;
import zeldaswordskills.entity.player.ZSSPlayerInfo;
import zeldaswordskills.entity.player.ZSSPlayerSkills;
import zeldaswordskills.handler.ZSSCombatEvents;
import zeldaswordskills.item.ItemHeldBlock;
import zeldaswordskills.item.ZSSItems;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.ModInfo;
import zeldaswordskills.skills.ICombo;
import zeldaswordskills.skills.ILockOnTarget;
import zeldaswordskills.skills.SkillBase;
import zeldaswordskills.util.TargetUtils;
/**
*
* Handles all client-sided events, such as render events, mouse event, etc.
*
*/
@SideOnly(Side.CLIENT)
public class ZSSClientEvents
{
private final Minecraft mc;
/** List of all GUI Overlays that may need rendering */
private final List<IGuiOverlay> overlays = new ArrayList<IGuiOverlay>();
/** List of GUI overlays that have rendered this tick */
private final List<IGuiOverlay> rendered = new ArrayList<IGuiOverlay>();
/** True when openGL matrix needs to be popped */
private boolean needsPop;
/** Store the current key code for mouse buttons */
private int mouseKey;
/** Whether the button during mouse event is Minecraft's keyBindAttack */
private boolean isAttackKey;
/** Whether the button during mouse event is Minecraft's keyBindUseItem*/
private boolean isUseKey;
public ZSSClientEvents() {
this.mc = Minecraft.getMinecraft();
// Add overlays in order of rendering priority (generally bigger is higher priority)
GuiMagicMeter meter = new GuiMagicMeter(mc);
overlays.add(meter);
overlays.add(new GuiMagicMeterText(mc, meter));
overlays.add(new GuiBuffBar(mc));
overlays.add(new GuiItemModeOverlay(mc));
overlays.add(new ComboOverlay(mc));
overlays.add(new GuiEndingBlowOverlay(mc));
}
@SubscribeEvent
public void onRenderExperienceBar(RenderGameOverlayEvent.Post event) {
if (event.type != RenderGameOverlayEvent.ElementType.EXPERIENCE) {
return;
}
for (IGuiOverlay overlay : this.overlays) {
if (overlay.shouldRender() && overlay.renderOverlay(event.resolution, this.rendered)) {
this.rendered.add(overlay);
}
}
this.rendered.clear();
}
@SubscribeEvent
public void onBakeModel(ModelBakeEvent event) {
for (ModelResourceLocation resource : ClientProxy.smartModels.keySet()) {
Object object = event.modelRegistry.getObject(resource);
if (object instanceof IBakedModel) {
Class<? extends IBakedModel> clazz = ClientProxy.smartModels.get(resource);
try {
IBakedModel customRender = clazz.getConstructor(IBakedModel.class).newInstance((IBakedModel) object);
event.modelRegistry.putObject(resource, customRender);
ZSSMain.logger.debug("Registered new renderer for resource " + resource + ": " + customRender.getClass().getSimpleName());
} catch (NoSuchMethodException e) {
ZSSMain.logger.warn("Failed to swap model: class " + clazz.getSimpleName() + " is missing a constructor that takes an IBakedModel");
} catch (Exception e) {
ZSSMain.logger.warn(String.format("Failed to swap model for %s with exception: %s", resource.toString(), e.getMessage()));
e.printStackTrace();
}
} else {
ZSSMain.logger.warn("Resource is not a baked model! Failed resource: " + resource.toString());
}
}
}
@SubscribeEvent
public void onStitchTexture(TextureStitchEvent.Pre event) {
String digit = "items/digits/";
for (int i = 0; i < 10; ++i) {
event.map.registerSprite(new ResourceLocation(ModInfo.ID, digit + i));
}
}
/**
* Attacks current target if player not currently using an item and {@link ICombo#onAttack}
* doesn't return false (i.e. doesn't miss)
* @param skill must implement BOTH {@link ILockOnTarget} AND {@link ICombo}
*/
@SideOnly(Side.CLIENT)
public static void performComboAttack(Minecraft mc, ILockOnTarget skill) {
if (!mc.thePlayer.isUsingItem()) {
mc.thePlayer.swingItem();
ZSSCombatEvents.setPlayerAttackTime(mc.thePlayer);
if (skill instanceof ICombo && ((ICombo) skill).onAttack(mc.thePlayer)) {
Entity entity = TargetUtils.getMouseOverEntity();
mc.playerController.attackEntity(mc.thePlayer, (entity != null ? entity : skill.getCurrentTarget()));
}
}
}
/**
* FOV is determined initially in EntityPlayerSP; fov is recalculated for
* the vanilla bow only in the case that zoom-enhancing gear is worn
*/
@SubscribeEvent
public void updateFOV(FOVUpdateEvent event) {
ItemStack stack = (event.entity.isUsingItem() ? event.entity.getItemInUse() : null);
if (stack != null) {
boolean flag = stack.getItem() instanceof IZoom;
if (flag || stack.getItem() == Items.bow) {
float magify = 1.0F;
for (ItemStack armor : event.entity.inventory.armorInventory) {
if (armor != null && armor.getItem() instanceof IZoomHelper) {
magify += ((IZoomHelper) armor.getItem()).getMagnificationFactor();
}
}
if (flag || magify != 1.0F) {
float maxTime = (flag ? ((IZoom) stack.getItem()).getMaxZoomTime() : 20.0F);
float factor = (flag ? ((IZoom) stack.getItem()).getZoomFactor() : 0.15F);
float charge = (float) event.entity.getItemInUseDuration() / maxTime;
IAttributeInstance iattributeinstance = event.entity.getEntityAttribute(SharedMonsterAttributes.movementSpeed);
float fov = (event.entity.capabilities.isFlying ? 1.1F : 1.0F);
fov *= (iattributeinstance.getAttributeValue() / (double) event.entity.capabilities.getWalkSpeed() + 1.0D) / 2.0D;
if (event.entity.capabilities.getWalkSpeed() == 0.0F || Float.isNaN(fov) || Float.isInfinite(fov)) {
fov = 1.0F;
}
if (charge > 1.0F) {
charge = 1.0F;
} else {
charge *= charge;
}
event.newfov = fov * (1.0F - charge * factor * magify);
}
}
}
}
/**
* Handles mouse clicks for skills, canceling where appropriate; note that while the player
* is locked on with a targeting skill, keyBindAttack will ALWAYS be canceled, as the attack
* is passed to {@link #performComboAttack}; if the event were left uncanceled, the attack
* would process again in the vanilla system, doubling durability damage to weapons
* @buttons no button clicked -1, left button 0, right click 1, middle click 2, possibly 3+ for other buttons
* @notes Corresponding key codes for the mouse in Minecraft are (event.button -100)
*/
@SubscribeEvent
public void onMouseChanged(MouseEvent event) {
mouseKey = event.button - 100;
isAttackKey = (mouseKey == mc.gameSettings.keyBindAttack.getKeyCode());
isUseKey = (mouseKey == mc.gameSettings.keyBindUseItem.getKeyCode());
if (event.dwheel == 0) {
// no wheel, no button: just moving the mouse around
if (event.button == -1) {
return;
} else if (!isAttackKey && !isUseKey) {
// no wheel, unchecked key bound to mouse: pass input to custom key handler, as KeyInputEvent no longer receives these automatically
if (event.buttonstate) {
ZSSKeyHandler.onKeyPressed(mc, mouseKey);
}
return;
}
}
ZSSPlayerSkills skills = ZSSPlayerSkills.get(mc.thePlayer);
// check pre-conditions for attacking and item use (not stunned, etc.):
if (event.buttonstate || event.dwheel != 0) {
if (isAttackKey) {
// hack for spin attack: allows key press information to be received while animating
if (skills.isSkillActive(SkillBase.spinAttack) && skills.getActiveSkill(SkillBase.spinAttack).isAnimating()) {
skills.getActiveSkill(SkillBase.spinAttack).keyPressed(mc, mc.gameSettings.keyBindAttack, mc.thePlayer);
event.setCanceled(true);
} else if (skills.isSkillActive(SkillBase.backSlice) && skills.getActiveSkill(SkillBase.backSlice).isAnimating()) {
skills.getActiveSkill(SkillBase.backSlice).keyPressed(mc, mc.gameSettings.keyBindAttack, mc.thePlayer);
event.setCanceled(true);
} else if (!skills.canInteract() || ZSSEntityInfo.get(mc.thePlayer).isBuffActive(Buff.STUN)) {
//LogHelper.info("Skills could not interact during left click - canceling");
event.setCanceled(true);
} else {
Item heldItem = (mc.thePlayer.getHeldItem() != null ? mc.thePlayer.getHeldItem().getItem() : null);
event.setCanceled(heldItem instanceof ItemHeldBlock || (!ZSSPlayerInfo.get(mc.thePlayer).canAttack() && (Config.affectAllSwings() || heldItem instanceof ISwingSpeed)));
}
} else if (isUseKey) {
event.setCanceled(!skills.canInteract() || ZSSEntityInfo.get(mc.thePlayer).isBuffActive(Buff.STUN));
} else { // cancel mouse wheel while animations are in progress
event.setCanceled(!skills.canInteract());
}
}
if (event.isCanceled() || !event.buttonstate) {
return;
}
ILockOnTarget skill = skills.getTargetingSkill();
if (skill != null && skill.isLockedOn()) {
if (isAttackKey) {
// mouse attack will always be canceled while locked on, as the click has been handled
if (Config.allowVanillaControls) {
if (!skills.onKeyPressed(mc, mc.gameSettings.keyBindAttack)) {
// no skill activated - perform a 'standard' attack
performComboAttack(mc, skill);
}
// hack for Armor Break: allows charging to begin without having to press attack key a second time
if (skills.hasSkill(SkillBase.armorBreak)) {
skills.getActiveSkill(SkillBase.armorBreak).keyPressed(mc, mc.gameSettings.keyBindAttack, mc.thePlayer);
}
}
// if vanilla controls not enabled, mouse click is ignored (i.e. canceled)
// if vanilla controls enabled, mouse click was already handled - cancel
event.setCanceled(true);
} else if (isUseKey && Config.allowVanillaControls) {
if (!skills.canInteract()) {
event.setCanceled(true);
}
}
} else if (isAttackKey) { // not locked on to a target, normal item swing: set attack time only
ZSSCombatEvents.setPlayerAttackTime(mc.thePlayer);
}
}
@SubscribeEvent
public void getFogDensity(EntityViewRenderEvent.FogDensity event) {
if (event.entity instanceof EntityPlayer) {
EntityPlayer player = (EntityPlayer) event.entity;
ItemStack helm = player.getCurrentArmor(ArmorIndex.WORN_HELM);
if (helm != null && !player.isPotionActive(Potion.blindness)) {
if (event.block.getMaterial() == Material.lava && (helm.getItem() == ZSSItems.tunicGoronHelm || helm.getItem() == ZSSItems.maskGoron)) {
event.density = 0.85F;
GlStateManager.setFog(2048);
event.setCanceled(true);
}
}
}
}
/*
// TODO
@SubscribeEvent
public void onRenderPlayer(RenderPlayerEvent.Pre event) {
ItemStack mask = event.entityPlayer.getCurrentArmor(ArmorIndex.WORN_HELM);
if (mask != null && mask.getItem() == ZSSItems.maskGiants) {
GlStateManager.pushMatrix();
needsPop = true;
if (event.entityPlayer == mc.thePlayer) {
if (mc.inGameHasFocus) {
GlStateManager.translate(0.0F, -6.325F, 0.0F);
GlStateManager.scale(3.0F, 3.0F, 3.0F);
}
} else {
GlStateManager.scale(3.0F, 3.0F, 3.0F);
}
/*
if (fakePlayer == null) {
fakePlayer = new FakeClientPlayer(event.entityPlayer.worldObj);
}
Vec3 vec3 = event.entityPlayer.getLookVec();
double dx = (Minecraft.getMinecraft().gameSettings.thirdPersonView > 0 ? 0.0D : vec3.xCoord);
double dz = (Minecraft.getMinecraft().gameSettings.thirdPersonView > 0 ? 0.0D : vec3.zCoord);
fakePlayer.setLocationAndAngles(event.entityPlayer.posX + dx, event.entityPlayer.posY, event.entityPlayer.posZ + dz, event.entityPlayer.rotationYaw, event.entityPlayer.rotationPitch);
fakePlayer.renderYawOffset = event.entityPlayer.renderYawOffset;
//fakePlayer.prevCameraPitch = event.entityPlayer.prevCameraPitch;
//fakePlayer.prevRotationPitch = event.entityPlayer.prevRotationPitch;
fakePlayer.prevRotationYaw = event.entityPlayer.prevRotationYaw;
fakePlayer.rotationYawHead = event.entityPlayer.rotationYawHead;
fakePlayer.prevPosX = event.entityPlayer.prevPosX + dx;
fakePlayer.prevPosY = event.entityPlayer.prevPosY;
fakePlayer.prevPosZ = event.entityPlayer.prevPosZ + dz;
Minecraft.getMinecraft().renderViewEntity = fakePlayer;
}
else if (fakePlayer != null) {
fakePlayer = null;
}*
}
}
@SubscribeEvent
public void onRenderPlayer(RenderPlayerEvent.Post event) {
if (needsPop) {
GlStateManager.popMatrix();
needsPop = false;
}
}
*/
}