public class BlockGrappleModifier extends Block {

	public BlockGrappleModifier() {
		super(Material.ROCK);
		setCreativeTab(grapplemod.tabGrapplemod);
	}

	@Override
	public boolean hasTileEntity(IBlockState state) {
		return true;
	}

	// Called when the block is placed or loaded client side to get the tile entity
	// for the block
	// Should return a new instance of the tile entity for the block
	@Override
	public TileEntity createTileEntity(World world, IBlockState state) {
		return new TileEntityGrappleModifier();
	}

	// the block will render in the SOLID layer. See
	// http://greyminecraftcoder.blogspot.co.at/2014/12/block-rendering-18.html for
	// more information.
	@SideOnly(Side.CLIENT)
	public BlockRenderLayer getBlockLayer() {
		return BlockRenderLayer.SOLID;
	}

	@Override
	public boolean isOpaqueCube(IBlockState state) {
		return true;
	}

	@Override
	public boolean isFullCube(IBlockState state) {
		return true;
	}

	// render using a BakedModel
	// not required because the default (super method) is MODEL
	@Override
	public EnumBlockRenderType getRenderType(IBlockState iBlockState) {
		return EnumBlockRenderType.MODEL;
	}
	
	@Override
	public void getDrops(NonNullList<ItemStack> drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune) {
		super.getDrops(drops, world, pos, state, fortune);
		
		TileEntity ent = world.getTileEntity(pos);
		TileEntityGrappleModifier tileent = (TileEntityGrappleModifier) ent;
		
		for (grapplemod.upgradeCategories category : grapplemod.upgradeCategories.values()) {
			if (tileent.unlockedCategories.containsKey(category) && tileent.unlockedCategories.get(category)) {
				drops.add(new ItemStack(category.getItem()));
			}
		}
	}
	
    @Override
    public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest)
    {
        if (willHarvest) return true; //If it will harvest, delay deletion of the block until after getDrops
        return super.removedByPlayer(state, world, pos, player, willHarvest);
    }
    /**
     * Spawns the block's drops in the world. By the time this is called the Block has possibly been set to air via
     * Block.removedByPlayer
     */
    @Override
    public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack tool)
    {
        super.harvestBlock(world, player, pos, state, te, tool);
        world.setBlockToAir(pos);
    }
	
	@Override
	public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn,
			EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
		ItemStack helditemstack = playerIn.getHeldItemMainhand();
		Item helditem = helditemstack.getItem();

		if (helditem instanceof BaseUpgradeItem) {
			if (!worldIn.isRemote) {
				TileEntity ent = worldIn.getTileEntity(pos);
				TileEntityGrappleModifier tileent = (TileEntityGrappleModifier) ent;
				
				grapplemod.upgradeCategories category = ((BaseUpgradeItem) helditem).category;
				
				if (tileent.isUnlocked(category)) {
					playerIn.sendMessage(new TextComponentString("Already has upgrade: " + category.description));
				} else {
					if (!playerIn.capabilities.isCreativeMode) {
						playerIn.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
					}
					
					tileent.unlockCategory(category);
					
					playerIn.sendMessage(new TextComponentString("Applied upgrade: " + category.description));
				}
			}
		} else if (helditem instanceof grappleBow) {
			if (!worldIn.isRemote) {
				TileEntity ent = worldIn.getTileEntity(pos);
				TileEntityGrappleModifier tileent = (TileEntityGrappleModifier) ent;
				
				GrappleCustomization custom = tileent.customization;
				NBTTagCompound nbt = custom.writeNBT();
				
				helditemstack.setTagCompound(nbt);
				
				playerIn.sendMessage(new TextComponentString("Applied configuration"));
			}
		} else if (helditem == Items.DIAMOND_BOOTS) {
			if (!worldIn.isRemote) {
				if (GrappleConfig.getconf().longfallbootsrecipe) {
					boolean gaveitem = false;
					if (helditemstack.isItemEnchanted()) {
						Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(helditemstack);
						if (enchantments.containsKey(Enchantments.FEATHER_FALLING)) {
							if (enchantments.get(Enchantments.FEATHER_FALLING) >= 4) {
								ItemStack newitemstack = new ItemStack(grapplemod.longfallboots);
								EnchantmentHelper.setEnchantments(enchantments, newitemstack);
								playerIn.setHeldItem(EnumHand.MAIN_HAND, newitemstack);
								gaveitem = true;
							}
						}
					}
					if (!gaveitem) {
						playerIn.sendMessage(new TextComponentString("Right click with diamond boots enchanted with feather falling IV to get long fall boots"));
					}
				} else {
					playerIn.sendMessage(new TextComponentString("Making long fall boots this way was disabled in the config. It probably has been replaced by a crafting recipe."));
				}
			}
		} else {
			TileEntity ent = worldIn.getTileEntity(pos);
			TileEntityGrappleModifier tileent = (TileEntityGrappleModifier) ent;
			
			grapplemod.proxy.openModifierScreen(tileent);
		}
		return true;

	}
} public class TileEntityGrappleModifier extends TileEntity {
	public HashMap<grapplemod.upgradeCategories, Boolean> unlockedCategories = new HashMap<grapplemod.upgradeCategories, Boolean>();
	public GrappleCustomization customization;
	
	public TileEntityGrappleModifier() {
		this.customization = new GrappleCustomization();
	}

	public void unlockCategory(upgradeCategories category) {
		unlockedCategories.put(category, true);
		this.sendUpdates();
	}
	
	public void setCustomizationClient(GrappleCustomization customization) {
		this.customization = customization;
		grapplemod.network.sendToServer(new GrappleModifierMessage(this.pos, this.customization));
		this.sendUpdates();
	}

	public void setCustomizationServer(GrappleCustomization customization) {
		this.customization = customization;
		this.sendUpdates();
	}

	private void sendUpdates() {
//		this.world.markBlockRangeForRenderUpdate(pos, pos);
		this.world.notifyBlockUpdate(pos, this.world.getBlockState(this.pos), this.world.getBlockState(this.pos), 3);
//		this.world.scheduleBlockUpdate(pos,this.getBlockType(),0,0);
		markDirty();
	}
	
	public boolean isUnlocked(upgradeCategories category) {
		return this.unlockedCategories.containsKey(category) && this.unlockedCategories.get(category);
	}

	// https://github.com/TheGreyGhost/MinecraftByExample/blob/master/src/main/java/minecraftbyexample/mbe20_tileentity_data/TileEntityData.java

	// When the world loads from disk, the server needs to send the TileEntity
	// information to the client
	// it uses getUpdatePacket(), getUpdateTag(), onDataPacket(), and
	// handleUpdateTag() to do this:
	// getUpdatePacket() and onDataPacket() are used for one-at-a-time TileEntity
	// updates
	// getUpdateTag() and handleUpdateTag() are used by vanilla to collate together
	// into a single chunk update packet
	@Override
	@Nullable
	public SPacketUpdateTileEntity getUpdatePacket() {
		NBTTagCompound nbtTagCompound = new NBTTagCompound();
		writeToNBT(nbtTagCompound);
		int metadata = getBlockMetadata();
		return new SPacketUpdateTileEntity(this.pos, metadata, nbtTagCompound);
	}

	@Override
	public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
		readFromNBT(pkt.getNbtCompound());
	}

	/*
	 * Creates a tag containing the TileEntity information, used by vanilla to
	 * transmit from server to client
	 */
	@Override
	public NBTTagCompound getUpdateTag() {
		NBTTagCompound nbtTagCompound = new NBTTagCompound();
		writeToNBT(nbtTagCompound);
		return nbtTagCompound;
	}

	/*
	 * Populates this TileEntity with information from the tag, used by vanilla to
	 * transmit from server to client
	 */
	@Override
	public void handleUpdateTag(NBTTagCompound tag) {
		this.readFromNBT(tag);
	}

	// This is where you save any data that you don't want to lose when the tile
	// entity unloads
	// In this case, we only need to store the ticks left until explosion, but we
	// store a bunch of other
	// data as well to serve as an example.
	// NBTexplorer is a very useful tool to examine the structure of your NBT saved
	// data and make sure it's correct:
	// http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-tools/1262665-nbtexplorer-nbt-editor-for-windows-and-mac
	@Override
	public NBTTagCompound writeToNBT(NBTTagCompound parentNBTTagCompound) {
		super.writeToNBT(parentNBTTagCompound); // The super call is required to save the tiles location
		
		NBTTagCompound unlockedNBT = parentNBTTagCompound.getCompoundTag("unlocked");
		
		for (grapplemod.upgradeCategories category : grapplemod.upgradeCategories.values()) {
			String num = String.valueOf(category.toInt());
			boolean unlocked = this.isUnlocked(category);
			
			unlockedNBT.setBoolean(num, unlocked);
		}
		
		parentNBTTagCompound.setTag("unlocked", unlockedNBT);
		parentNBTTagCompound.setTag("customization", this.customization.writeNBT());
		
		
		
		return parentNBTTagCompound;
	}

	// This is where you load the data that you saved in writeToNBT
	@Override
	public void readFromNBT(NBTTagCompound parentNBTTagCompound) {
		super.readFromNBT(parentNBTTagCompound); // The super call is required to load the tiles location
		
		NBTTagCompound unlockedNBT = parentNBTTagCompound.getCompoundTag("unlocked");
		
		for (grapplemod.upgradeCategories category : grapplemod.upgradeCategories.values()) {
			String num = String.valueOf(category.toInt());
			boolean unlocked = unlockedNBT.getBoolean(num);
			
			this.unlockedCategories.put(category, unlocked);
		}
		
		NBTTagCompound custom = parentNBTTagCompound.getCompoundTag("customization");
		this.customization.loadNBT(custom);
	}
} public class airfrictionController extends grappleController {
	public final double playermovementmult = 0.5;
	
	public int ignoregroundcounter = 0;
	public boolean was_sliding = false;
	public boolean was_wallrunning = false;
	public boolean was_rocket = false;
	public boolean first_tick_since_created = true;
	
	public airfrictionController(int arrowId, int entityId, World world, vec pos, int id, GrappleCustomization custom) {
		super(arrowId, entityId, world, pos, id, custom);
	}
	
	@Override
	public void updatePlayerPos() {
		Entity entity = this.entity;
		
		if (entity == null) {return;}
		
		if (entity.isRiding()) {
			this.unattach();
			this.updateServerPos();
			return;
		}

		vec additionalmotion = new vec(0,0,0);
		
		if (GrappleConfig.getconf().dont_override_movement_in_air && !entity.onGround && !was_sliding && !was_wallrunning && !was_rocket && !first_tick_since_created) {
			motion = vec.motionvec(entity);
			this.unattach();
			return;
		}

		if (this.attached) {
			boolean issliding = grapplemod.proxy.issliding(entity, motion);
			
			if (issliding && !was_sliding) {
				playSlideSound();
			}
			
			if (this.ignoregroundcounter <= 0) {
				this.normalGround(issliding);					
				this.normalCollisions(issliding);
			}

			this.applyAirFriction();
			
			if (this.entity.isInWater() || this.entity.isInLava()) {
				this.unattach();
				return;
			}

			if (entity instanceof EntityLivingBase && ((EntityLivingBase) entity).isOnLadder()) {
				this.unattach();
				return;
			}
			
			boolean doesrocket = false;
			if (this.custom != null) {
				if (this.custom.rocket) {
					vec rocket = this.rocket(entity);
					this.motion.add_ip(rocket);
					if (rocket.length() > 0) {
						doesrocket = true;
					}
				}
			}

			if (issliding) {
				this.applySlidingFriction();
			}

			boolean wallrun = this.applywallrun();

			if (!issliding && !was_sliding) {
				if (wallrun) {
					motion = motion.removealong(new vec(0,1,0));
					if (this.walldirection != null) {
						motion = motion.removealong(this.walldirection);
					}

					vec new_movement = this.playermovement.changelen(GrappleConfig.getconf().wallrun_speed*1.5);
					if (this.walldirection != null) {
						new_movement = new_movement.removealong(this.walldirection);
					}
					if (new_movement.length() > GrappleConfig.getconf().wallrun_speed) {
						new_movement.changelen_ip(GrappleConfig.getconf().wallrun_speed);
					}
					vec current_motion_along = this.motion.removealong(new vec(0,1,0));
					vec new_motion_along = this.motion.add(new_movement).removealong(new vec(0,1,0));
					if (this.walldirection != null) {
						current_motion_along = current_motion_along.removealong(this.walldirection);
						new_motion_along = new_motion_along.removealong(this.walldirection);
					}
					if (current_motion_along.length() <= GrappleConfig.getconf().wallrun_max_speed || current_motion_along.dot(new_movement) < 0) {
						motion.add_ip(new_movement);
						if (new_motion_along.length() > GrappleConfig.getconf().wallrun_max_speed) {
							this.motion.changelen_ip(GrappleConfig.getconf().wallrun_max_speed);
						}
					}
					additionalmotion.add_ip(wallrun_press_against_wall());
				} else {
					double max_motion = GrappleConfig.getconf().airstrafe_max_speed;
					double accel = GrappleConfig.getconf().airstrafe_acceleration;
					vec motion_horizontal = motion.removealong(new vec(0,1,0));
					double prev_motion = motion_horizontal.length();
					vec new_motion_horizontal = motion_horizontal.add(this.playermovement.changelen(accel));
					double angle = motion_horizontal.angle(new_motion_horizontal);
					if (new_motion_horizontal.length() > max_motion && new_motion_horizontal.length() > prev_motion) {
						double ninety_deg = Math.PI / 2;
						double new_max_motion = max_motion;
						if (angle < ninety_deg && prev_motion > max_motion) {
							new_max_motion = prev_motion + ((max_motion - prev_motion) * (angle / (Math.PI / 2)));
						}
						new_motion_horizontal.changelen_ip(new_max_motion);
					}
					motion.x = new_motion_horizontal.x;
					motion.z = new_motion_horizontal.z;
				}
			}
			
			if (entity instanceof EntityLivingBase) {
				EntityLivingBase entityliving = (EntityLivingBase) entity;
				if (entityliving.isElytraFlying()) {
					this.unattach();
				}
			}
			
			vec gravity = new vec(0, -0.05, 0);

			if (!wallrun) {
				motion.add_ip(gravity);
			}

			vec newmotion;
			
			newmotion = motion.add(additionalmotion);
			
//			if (wallrun) {
//				newmotion.add_ip(this.walldirection);
//			}

			entity.motionX = newmotion.x;
			entity.motionY = newmotion.y;
			entity.motionZ = newmotion.z;
			
			this.updateServerPos();
			
			if (entity.onGround) {
				if (!issliding) {
					if (!wallrun) {
						if (!doesrocket) {
							if (ignoregroundcounter <= 0) {
								this.unattach();
							}
						} else {
							motion = vec.motionvec(entity);
						}
					}
				}
			}
			if (ignoregroundcounter > 0) { ignoregroundcounter--; }
			
			was_sliding = issliding;
			was_wallrunning = wallrun;
			was_rocket = doesrocket;
			first_tick_since_created = false;
		}
	}

	public void receiveEnderLaunch(double x, double y, double z) {
		super.receiveEnderLaunch(x, y, z);
		this.ignoregroundcounter = 2;
	}
	
	public void slidingJump() {
		super.slidingJump();
		this.ignoregroundcounter = 2;
	}
	
	public void playSlideSound() {
		grapplemod.proxy.playSlideSound(this.entity);
	}
} public class grappleController {
//	public int arrowId;
	public int entityId;
	public World world;
//	public vec pos;
	
//	public grappleArrow arrow;
	public Entity entity;
	
	public HashSet<grappleArrow> arrows = new HashSet<grappleArrow>();
	public HashSet<Integer> arrowIds = new HashSet<Integer>();
	
	public boolean attached = true;
	
//	public double r;
	public vec motion;
	
	public double playerforward = 0;
	public double playerstrafe = 0;
	public boolean playerjump = false;
	public boolean playersneak = false;
//	public boolean waitingonplayerjump = false;
	public vec playermovement_unrotated = new vec(0,0,0);
	public vec playermovement = new vec(0,0,0);
	
//	public int counter = 0;
	public int ongroundtimer = 0;
	public int maxongroundtimer = 3;
	
	public double maxlen;
	
	public double playermovementmult = 0;
	
	public int controllerid;
	
//	public ClientProxyClass clientproxy = null;
	
//	public final double playermovementmult = 0.5;
	
//	public SegmentHandler segmenthandler;
	
	public GrappleCustomization custom = null;
	
	public grappleController(int arrowId, int entityId, World world, vec pos, int controllerid, GrappleCustomization custom) {
		this.entityId = entityId;
		this.world = world;
//		this.pos = pos;
		this.custom = custom;
		
		if (this.custom != null) {
			this.playermovementmult = this.custom.playermovementmult;
			this.maxlen = custom.maxlen;
		}
		
		this.controllerid = controllerid;
		
		this.entity = world.getEntityByID(entityId);
//		grappleArrow arrow = (grappleArrow) world.getEntityByID(arrowId);
//		if (arrow != null) {
//			((grappleArrow) arrow).segmenthandler = this.segmenthandler;
//			this.segmenthandler = ((grappleArrow) arrow).segmenthandler;
//			arrow.r = ((grappleArrow) arrow).segmenthandler.getDist(this.pos, vec.positionvec(entity).add(new vec(0, entity.getEyeHeight(), 0)));
//		}
		
//		this.r = this.pos.sub(vec.positionvec(entity)).length();
		this.motion = vec.motionvec(entity);
		
		// undo friction
		vec newmotion = new vec(entity.posX - entity.prevPosX, entity.posY - entity.prevPosY, entity.posZ - entity.prevPosZ);
		if (newmotion.x/motion.x < 2 && motion.x/newmotion.x < 2 && newmotion.y/motion.y < 2 && motion.y/newmotion.y < 2 && newmotion.z/motion.z < 2 && motion.z/newmotion.z < 2) {
			this.motion = newmotion;
		}
//		double f6 = 0.91F;
//        if (entity.onGround)
//        {
//            BlockPos.PooledMutableBlockPos blockpos$pooledmutableblockpos = BlockPos.PooledMutableBlockPos.retain(entity.posX, entity.getEntityBoundingBox().minY - 1.0D, entity.posZ);
//            IBlockState underState = this.world.getBlockState(blockpos$pooledmutableblockpos.setPos(entity.posX, entity.getEntityBoundingBox().minY - 1.0D, entity.posZ));
//            f6 = underState.getBlock().getSlipperiness(underState, entity.world, blockpos$pooledmutableblockpos, entity);// * 0.91F;
//        }
//        motion.y /= 0.9800000190734863D;
//        motion.x /= (double)f6;
//        motion.z /= (double)f6;
//        this.applyAirFriction();

		this.ongroundtimer = 0;
		
		grapplemod.registerController(this.entityId, this);
		
//		if (grapplemod.proxy instanceof ClientProxyClass) {
//			this.clientproxy = (ClientProxyClass) grapplemod.proxy;
//		}
		
		if (arrowId != -1) {
			Entity arrowentity = world.getEntityByID(arrowId);
			if (arrowentity != null && !arrowentity.isDead && arrowentity instanceof grappleArrow) {
				this.addArrow((grappleArrow)arrowentity);
			} else {
				this.unattach();
			}
		}
		
		if (custom != null && custom.rocket) {
			grapplemod.proxy.updateRocketRegen(custom.rocket_active_time, custom.rocket_refuel_ratio);
		}
	}
	
	public void unattach() {
		if (grapplemod.controllers.containsValue(this)) {
			this.attached = false;
			
			grapplemod.unregisterController(this.entityId);
			
			if (this.controllerid != grapplemod.AIRID) {
				grapplemod.network.sendToServer(new GrappleEndMessage(this.entityId, this.arrowIds));
				grapplemod.proxy.createControl(grapplemod.AIRID, -1, this.entityId, this.entity.world, new vec(0,0,0), null, this.custom);
			}
		}
	}
	
/*	public grappleArrow getArrow() {
		return (grappleArrow) world.getEntityByID(arrowId);
	}*/
	
	public void doClientTick() {
		if (this.attached) {
			if (this.entity == null || this.entity.isDead) {
				this.unattach();
			} else {
//				grapplemod.proxy.getplayermovement(this, this.entityId);
				this.updatePlayerPos();
			}
		}
	}
		
	public void receivePlayerMovementMessage(float strafe,
			float forward, boolean jump, boolean sneak) {
		playerforward = forward;
		playerstrafe = strafe;
		playersneak = sneak;
//		if (!jump) {
//			playerjump = false;
//		} else if (jump && !playerjump) {
//			playerjump = true;
//			waitingonplayerjump = true;
//		}
		playermovement_unrotated = new vec(strafe, 0, forward);
		playermovement = playermovement_unrotated.rotate_yaw((float) (this.entity.rotationYaw * (Math.PI / 180.0)));
	}
	
//	public boolean isjumping() {
//		if (playerjump && waitingonplayerjump) {
//			waitingonplayerjump = false;
//			return true;
//		}
//		return false;
//	}
		
	public void updatePlayerPos() {
		Entity entity = this.entity;
		
		if (this.attached) {
			if(entity != null) {
				if (true) {
					if (entity.isRiding()) {
						this.unattach();						
						this.updateServerPos();
						return;
					}
					
					this.normalGround(false);
					this.normalCollisions(false);
					this.applyAirFriction();
					
					vec playerpos = vec.positionvec(entity);
					playerpos = playerpos.add(new vec(0, entity.getEyeHeight(), 0));
					

//					Vec3 playermotion = new Vec3(entity.motionX, entity.motionY, entity.motionZ);
					
					vec additionalmotion = new vec(0,0,0);
					
					vec gravity = new vec(0, -0.05, 0);

					
//					if (!(this.ongroundtimer > 0)) {
						motion.add_ip(gravity);
//					}
					
					boolean doJump = false;
					double jumpSpeed = 0;
					boolean isClimbing = false;
					
					// is motor active? (check motorwhencrouching / motorwhennotcrouching)
					boolean motor = false;
					if (this.custom.motor) {
						if (ClientProxyClass.key_motoronoff.isKeyDown() && this.custom.motorwhencrouching) {
							motor = true;
						} else if (!ClientProxyClass.key_motoronoff.isKeyDown() && this.custom.motorwhennotcrouching) {
							motor = true;
						}
					}
					
//					double curspeed = 0;
					boolean close = false;
					
					vec averagemotiontowards = new vec(0, 0, 0);
					
					double min_spherevec_dist = 99999;
					
					for (grappleArrow arrow : this.arrows) {
						vec arrowpos = vec.positionvec(arrow);//this.getPositionVector();
						
						// Update segment handler (handles rope bends)
						if (this.custom.phaserope) {
							arrow.segmenthandler.updatepos(arrowpos, playerpos, arrow.r);
						} else {
							arrow.segmenthandler.update(arrowpos, playerpos, arrow.r, false);
						}
						
						// vectors along rope
						vec anchor = arrow.segmenthandler.getclosest(arrowpos);
						double distToAnchor = arrow.segmenthandler.getDistToAnchor();
						double remaininglength = motor ? Math.max(this.custom.maxlen, arrow.r) - distToAnchor : arrow.r - distToAnchor;
						
						vec oldspherevec = playerpos.sub(anchor);
						vec spherevec = oldspherevec.changelen(remaininglength);
						vec spherechange = spherevec.sub(oldspherevec);
//						Vec3 spherepos = spherevec.add(arrowpos);
						
						if (spherevec.length() < min_spherevec_dist) {min_spherevec_dist = spherevec.length();}
						
						averagemotiontowards.add_ip(spherevec.changelen(-1));
						
						if (motor) {
							arrow.r = distToAnchor + oldspherevec.length();
						}
						
//						averagemotiontowards.add_ip(spherevec.changelen(-1));
						
//						curspeed += motion.proj(oldspherevec).length();
						
						// snap to rope length
						if (oldspherevec.length() < remaininglength) {
						} else {
							if (oldspherevec.length() - remaininglength > GrappleConfig.getconf().rope_snap_buffer) {
								// if rope is too long, the rope snaps
								
								this.unattach();
								
								this.updateServerPos();
								return;
							} else {
								additionalmotion = spherechange;
							}
						}
						
						double dist = oldspherevec.length();
						
						this.calctaut(dist, arrow);

						// handle keyboard input (jumping and climbing)
						if (entity instanceof EntityPlayer) {
							EntityPlayer player = (EntityPlayer) entity;
							boolean isjumping = ClientProxyClass.key_jumpanddetach.isKeyDown();
							isjumping = isjumping && !playerjump; // only jump once when key is first pressed
							playerjump = ClientProxyClass.key_jumpanddetach.isKeyDown();
							if (isjumping) {
								// jumping
								if (ongroundtimer > 0) { // on ground: jump normally
									
								} else {
									double timer = this.entity.world.getTotalWorldTime() - ClientProxyClass.prev_rope_jump_time;
									if (timer > GrappleConfig.getconf().rope_jump_cooldown_s * 20.0) {
										doJump = true;
										jumpSpeed = this.getJumpPower(player, spherevec, arrow);
									}
								}
							}
							if (ClientProxyClass.key_slow.isKeyDown()) {
								// climbing
	//							motion = multvec(motion, 0.9);
								vec motiontorwards = spherevec.changelen(-0.1);
								motiontorwards = new vec(motiontorwards.x, 0, motiontorwards.z);
								if (motion.dot(motiontorwards) < 0) {
									motion.add_ip(motiontorwards);
								}

								vec newmotion = dampenmotion(motion, motiontorwards);
								motion = new vec(newmotion.x, motion.y, newmotion.z);
	//							motion = multvec(motion, 0.98);

							}
							if ((ClientProxyClass.key_climb.isKeyDown() || ClientProxyClass.key_climbup.isKeyDown() || ClientProxyClass.key_climbdown.isKeyDown()) && !motor) {
								isClimbing = true;
								if (anchor.y > playerpos.y) {
									// when shift is pressed, stop swinging
									
									// climb up/down rope
//									double climbup = 0;
//									if (ClientProxyClass.key_climbup.isKeyDown()) { playerforward = 1.0f; }
//									else if (ClientProxyClass.key_climbdown.isKeyDown()) { playerforward = -1.0f; }
//									if (playerforward != 0) {
//											if (dist + distToAnchor < maxlen || this.playerforward > 0 || maxlen == 0) {
									double climbup = 0;
									if (ClientProxyClass.key_climb.isKeyDown()) {
										climbup = playerforward;
										if (playersneak) {
											climbup = climbup / 0.3D;
										}
										if (climbup > 1) {climbup = 1;} else if (climbup < -1) {climbup = -1;}
									}
									else if (ClientProxyClass.key_climbup.isKeyDown()) { climbup = 1.0; }
									else if (ClientProxyClass.key_climbdown.isKeyDown()) { climbup = -1.0; }
									if (climbup != 0) {
										if (dist + distToAnchor < maxlen || climbup > 0 || maxlen == 0) {
												arrow.r = dist + distToAnchor;
												arrow.r -= climbup*GrappleConfig.getconf().climb_speed;
												if (arrow.r < distToAnchor) {
													arrow.r = dist + distToAnchor;
												}
												
												vec additionalmovementdown = spherevec.changelen(-climbup * GrappleConfig.getconf().climb_speed).proj(new vec(0,1,0));
												if (additionalmovementdown.y < 0) {
													additionalmotion.add_ip(additionalmovementdown);
												}
											}
									}
								}
							}
						}
						if (dist + distToAnchor < 2) {
							close = true;
						}
						
						// swing along max rope length
						if (anchor.sub(playerpos.add(motion)).length() > remaininglength) { // moving away
							motion = motion.removealong(spherevec);
						}
					}
					averagemotiontowards.changelen_ip(1);
					
//					curspeed = curspeed / this.arrows.size();
//					averagemotiontowards.mult_ip(1 / this.arrows.size());
					
		        	vec facing = new vec(entity.getLookVec()).normalize();
		        	
		        	// Motor
					if (motor) {
						boolean dopull = true;
						
						// if only one rope is pulling and not oneropepull, disable motor
						if (this.custom.doublehook && this.arrows.size() == 1) {
							boolean isdouble = true;
							for (grappleArrow arrow : this.arrows) {
								if (!arrow.isdouble) {
									isdouble = false;
								}
							}
							if (isdouble && !this.custom.oneropepull) {
								dopull = false;
							}
						}
						
/*						if (curspeed > this.custom.motormaxspeed) {
							motion.changelen_ip(this.custom.motormaxspeed);
						}*/
						
						vec totalpull = new vec(0, 0, 0);
						
						double accel = this.custom.motoracceleration / this.arrows.size();
						
						double minabssidewayspull = 999;
//						double maxabssidewayspull = 0;
						
						boolean firstpull = true;
						boolean pullispositive = true;
						boolean pullissameway = true;
						
						// set all motors to maximum pull and precalculate some stuff for smart motor / smart double motor
						for (grappleArrow arrow : this.arrows) {
							vec arrowpos = vec.positionvec(arrow);//this.getPositionVector();
							vec anchor = arrow.segmenthandler.getclosest(arrowpos);
							vec spherevec = playerpos.sub(anchor);
							vec pull = spherevec.mult(-1);
							
							arrow.pull = accel;
							
							totalpull.add_ip(pull.changelen(accel));
							
							pull.changelen_ip(arrow.pull);

							// precalculate some stuff for smart double motor
							// For smart double motor: the motors should pull left and right equally
							// one side will be less able to pull to its side due to the angle
							// therefore the other side should slow down in order to match and have both sides pull left/right equally
							// the amount each should pull (the lesser of the two) is minabssidewayspull
							if (pull.dot(facing) > 0 || this.custom.pullbackwards) {
								if (this.custom.smartdoublemotor && this.arrows.size() > 1) {
									vec facingxy = new vec(facing.x, 0, facing.z);
									vec facingside = facingxy.cross(new vec(0, 1, 0)).normalize();
//									vec pullxy = new vec(pull.x, 0, pull.z);
									vec sideways = pull.proj(facingside); // pullxy.removealong(facing);
									vec currentsideways = motion.proj(facingside);
									sideways.add_ip(currentsideways);
									double sidewayspull = sideways.dot(facingside); // facingxy.cross(sideways).y;
									
									if (Math.abs(sidewayspull) < minabssidewayspull) {
										minabssidewayspull = Math.abs(sidewayspull);
									}
/*									if (Math.abs(sidewayspull) > maxabssidewayspull) {
										maxabssidewayspull = Math.abs(sidewayspull);
									}*/
									
									if (firstpull) {
										firstpull = false;
										pullispositive = (sidewayspull >= 0);
									} else {
										if (pullispositive != (sidewayspull >= 0)) {
											pullissameway = false;
										}
									}
								}
								
							}
						}
						
						// Smart double motor - calculate the speed each motor should pull at
						if (this.custom.smartdoublemotor && this.arrows.size() > 1) {
							totalpull = new vec(0, 0, 0);
							
							for (grappleArrow arrow : this.arrows) {
								vec arrowpos = vec.positionvec(arrow);//this.getPositionVector();
								vec anchor = arrow.segmenthandler.getclosest(arrowpos);
								vec spherevec = playerpos.sub(anchor);
								vec pull = spherevec.mult(-1);
								pull.changelen_ip(arrow.pull);
								
								if (pull.dot(facing) > 0 || this.custom.pullbackwards) {
									vec facingxy = new vec(facing.x, 0, facing.z);
									vec facingside = facingxy.cross(new vec(0, 1, 0)).normalize();
//									vec pullxy = new vec(pull.x, 0, pull.z);
									vec sideways = pull.proj(facingside); // pullxy.removealong(facing);
									vec currentsideways = motion.proj(facingside);
									sideways.add_ip(currentsideways);
									double sidewayspull = sideways.dot(facingside); // facingxy.cross(sideways).y;
									
									if (pullissameway) {
										// only 1 rope pulls
										if (Math.abs(sidewayspull) > minabssidewayspull+0.05) {
											arrow.pull = 0;
										}
									} else {
										arrow.pull = arrow.pull * minabssidewayspull / Math.abs(sidewayspull);
									}
									totalpull.add_ip(pull.changelen(arrow.pull));
								} else {
									if (arrow.isdouble) {
										if (!this.custom.oneropepull) {
											dopull = false;
										}
									}
								}
							}
						}
						
						// smart motor - angle of motion = angle facing
						// match angle (the ratio of pulling upwards to pulling sideways)
						// between the motion (after pulling and gravity) vector and the facing vector
						// if double hooks, all hooks are scaled by the same amount (to prevent pulling to the left/right)
						double pullmult = 1;
						if (this.custom.smartmotor && totalpull.y > 0 && !(this.ongroundtimer > 0 || entity.onGround)) {
							vec pullxzvector = new vec(totalpull.x, 0, totalpull.z);
							double pullxz = pullxzvector.length();
							double motionxz = motion.proj(pullxzvector).dot(pullxzvector.normalize());
//							double facingxz = new vec(facing.x, 0, facing.z).length();//.proj(ropexz);
							double facingxz = facing.proj(pullxzvector).dot(pullxzvector.normalize());
//							double facinglmult = (totalpull.length()) / (facing.length());
//							double pulll = gravity.y / (facing.y * facinglmult - totalpull.y);
							
							
							
							// (newpully + gravityy) / newpullx = facingy / facingxz
							// newmotiony = totalpully * pullmult + motion.y
							// newpullxz = pullxz * pullmult + motionxz
							// (totalpully * pullmult + motion.y + gravityy) / (pullxz * pullmult + motionxz) = facingy / facingxz
							// (y          * m        + a        + g       ) / (x      * m        + b       ) = f       / w          (1-letter vars for wolframalpha)
							// m = (w (a + g) - b f)/(f x - w y)
							// pullmult = (facingxz * (motion.y + gravity.y) - motionxz * facing.y)/(facing.y * pullxz - facingxz * totalpull.y)
							
							pullmult = (facingxz * (motion.y + gravity.y) - motionxz * facing.y)/(facing.y * pullxz - facingxz * totalpull.y); // (gravity.y * facingxz) / (facing.y * pullxz - facingxz * totalpull.y);
							
							if ((facing.y * pullxz - facingxz * totalpull.y) == 0) {
								// division by zero
								pullmult = 9999;
							}
									
							double pulll = pullmult * totalpull.length();
							
							if (pulll > this.custom.motoracceleration) {
								pulll = this.custom.motoracceleration;
							}
							if (pulll < 0) {
								pulll = 0;
							}
							
							pullmult = pulll / totalpull.length();
						}
						
						// Prevent motor from moving too fast (motormaxspeed)
						if (this.motion.dot(totalpull) > 0) {
							if (this.motion.proj(totalpull).length() + totalpull.mult(pullmult).length() > this.custom.motormaxspeed) {
								pullmult = (this.custom.motormaxspeed - this.motion.proj(totalpull).length()) / totalpull.length();
								if (pullmult < 0) {
									pullmult = 0;
								}
							}
						}
						
						// sideways dampener
						if (this.custom.motordampener && totalpull.length() != 0) {
							motion = dampenmotion(motion, totalpull);
						}
						
						// actually pull with the motor
						if (dopull) {
							for (grappleArrow arrow : this.arrows) {
								vec arrowpos = vec.positionvec(arrow);//this.getPositionVector();
								vec anchor = arrow.segmenthandler.getclosest(arrowpos);
								vec spherevec = playerpos.sub(anchor);
								vec pull = spherevec.mult(-1);
								pull.changelen_ip(arrow.pull * pullmult);
								
//								System.out.print(arrow.pull * pullmult);
//								System.out.print(" ");
								
								if (pull.dot(facing) > 0 || this.custom.pullbackwards) {
									if (arrow.pull > 0) {
										motion.add_ip(pull);
									}
								}
							}
						}
						
						// if player is at the destination, slow down
						if (close && !(this.arrows.size() > 1)) {
							if (entity.collided || entity.onGround) {
								motion.mult_ip(0.6);
							}
						}
//						System.out.println();
					}
					
					// forcefield
					if (this.custom.repel) {
						vec blockpush = check_repel(playerpos, entity.world);
						blockpush.mult_ip(this.custom.repelforce * 0.5);
						blockpush = new vec(blockpush.x*0.5, blockpush.y*2, blockpush.z*0.5);
						this.motion.add_ip(blockpush);
					}
					
					// rocket
					if (this.custom.rocket) {
						this.motion.add_ip(this.rocket(entity));
					}
					
					// WASD movement
					if (!doJump && !isClimbing) {
						applyPlayerMovement();
					}
					
					// jump
					if (doJump) {
						if (jumpSpeed <= 0) {
							jumpSpeed = 0;
						}
						if (jumpSpeed > GrappleConfig.getconf().rope_jump_power) {
							jumpSpeed = GrappleConfig.getconf().rope_jump_power;
						}
						this.doJump(entity, jumpSpeed, averagemotiontowards, min_spherevec_dist);
						ClientProxyClass.prev_rope_jump_time = this.entity.world.getTotalWorldTime();
						return;
					}
					
					// now to actually apply everything to the player
					vec newmotion = motion.add(additionalmotion);
					
					if (Double.isNaN(newmotion.x) || Double.isNaN(newmotion.y) || Double.isNaN(newmotion.z)) {
						newmotion = new vec(0, 0, 0);
						motion = new vec(0, 0, 0);
						System.out.println("error: motion is NaN");
					}
					
//					entity.setVelocity(newmotion.xCoord, newmotion.yCoord, newmotion.zCoord);
					entity.motionX = newmotion.x;
					entity.motionY = newmotion.y;
					entity.motionZ = newmotion.z;
					
//					if (entity instanceof EntityPlayerMP) {
						
//						((EntityPlayerMP) entity).playerNetServerHandler.sendPacket(new S12PacketEntityVelocity(entity));
						
						/*
						counter++;
						if (counter > 100) {
							counter = 0;
							grapplemod.network.sendTo(new PlayerPosMessage(entity.getEntityId(), entity.posX, entity.posY, entity.posZ), (EntityPlayerMP) entity);
						}
						*/
//					}
					
//					entity.fallDistance = 0;
					
					this.updateServerPos();
				}
			}
		}
	}
	
	public void calctaut(double dist, grappleArrow arrow) {
		if (arrow != null) {
    		if (dist < arrow.r) {
    			double taut = 1 - ((arrow.r - dist) / 5);
    			if (taut < 0) {
    				taut = 0;
    			}
    			arrow.taut = taut;
    		} else {
    			arrow.taut = 1;
    		}
    	}
	}
	
//	boolean prevcollision = false;
//	vec prevcollisionpos = new vec(0,0,0);

	public void normalCollisions(boolean sliding) {
		// stop if collided with object
		vec pos = vec.positionvec(this.entity);
		if (entity.collidedHorizontally) {
//			if (refreshmotion || prevcollision) {
				if (entity.motionX == 0) {
					if (!sliding || this.tryStepUp(new vec(this.motion.x, 0, 0))) {
						this.motion.x = 0;
					}
				}
				if (entity.motionZ == 0) {
					if (!sliding || this.tryStepUp(new vec(0, 0, this.motion.z))) {
						this.motion.z = 0;
					}
				}
//			}
		}
		
		if (sliding && !entity.collidedHorizontally) {
			if (entity.posX - entity.prevPosX == 0) {
				this.motion.x = 0;
			}
			if (entity.posZ - entity.prevPosZ == 0) {
				this.motion.z = 0;
			}
		}
//		prevcollision = entity.collidedHorizontally;
//		if (prevcollision) {
//			if (entity.motionX == 0 || entity.motionZ == 0) {
//				prevcollisionpos = pos;
//			}
//		}
		if (entity.collidedVertically) {
			if (entity.onGround) {
				if (!sliding && GameSettings.isKeyDown(Minecraft.getMinecraft().gameSettings.keyBindJump)) {
					this.motion.y = entity.motionY;
				} else {
					if (this.motion.y < 0) {
						this.motion.y = 0;
					}
				}
			} else {
				if (this.motion.y > 0) {
					if (entity.lastTickPosY == entity.posY) {
						this.motion.y = 0;
					}
				}
			}
		}
	}
	
	public boolean tryStepUp(vec collisionmotion) {
		if (collisionmotion.length() == 0) {return false;}
		vec moveoffset = collisionmotion.changelen(0.05).add(0, entity.stepHeight+0.01, 0);
		if (this.world.getCollisionBoxes(this.entity, this.entity.getEntityBoundingBox().offset(moveoffset.x, moveoffset.y, moveoffset.z)).isEmpty()) {
			if (!this.entity.onGround) {
				entity.posX += moveoffset.x;
				entity.posY += moveoffset.y;
				entity.posZ += moveoffset.z;
				this.entity.setPosition(entity.posX, entity.posY, entity.posZ);
				entity.prevPosX = entity.posX;
				entity.prevPosY = entity.posY;
				entity.prevPosZ = entity.posZ;
			}
			entity.collidedHorizontally = false;
			return false;
		}
		return true;
	}
	
	boolean prevonground = false;

	public void normalGround(boolean sliding) {
		if (entity.onGround) {
			ongroundtimer = maxongroundtimer;
//			if (this.motion.y < 0) {
//				this.motion.y = 0;
//			}
		} else {
			if (this.ongroundtimer > 0) {
				ongroundtimer--;
			}
		}
		if (entity.onGround || ongroundtimer > 0) {
			if (!sliding) {
				this.motion = vec.motionvec(entity);
				if (Minecraft.getMinecraft().gameSettings.keyBindJump.isKeyDown()) {
					this.motion.y += 0.05;
				}
			}
		}
		prevonground = entity.onGround;
	}

	private double getJumpPower(Entity player, double jumppower) {
		double maxjump = GrappleConfig.getconf().rope_jump_power;
		if (ongroundtimer > 0) { // on ground: jump normally
			ongroundtimer = 20;
			return 0;
		}
		if (player.onGround) {
			jumppower = 0;
		}
		if (player.collided) {
			jumppower = maxjump;
		}
		if (jumppower < 0) {
			jumppower = 0;
		}
		
		return jumppower;
	}
	
	public void doJump(Entity player, double jumppower, vec averagemotiontowards, double min_spherevec_dist) {
		if (jumppower > 0) {
			if (GrappleConfig.getconf().rope_jump_at_angle && min_spherevec_dist > 1) {
				motion.add_ip(averagemotiontowards.changelen(jumppower));
				player.motionX = motion.x;
				player.motionY = motion.y;
				player.motionZ = motion.z;
			} else {
				if (jumppower > player.motionY + jumppower) {
					player.motionY = jumppower;
				} else {
					player.motionY += jumppower;
				}
			}
		}
		
		this.unattach();
		
		this.updateServerPos();
	}
	
	public double getJumpPower(Entity player, vec spherevec, grappleArrow arrow) {
		double maxjump = GrappleConfig.getconf().rope_jump_power;
		vec jump = new vec(0, maxjump, 0);
		if (spherevec != null && !GrappleConfig.getconf().rope_jump_at_angle) {
			jump = jump.proj(spherevec);
		}
		double jumppower = jump.y;
		
		if (spherevec != null && spherevec.y > 0) {
			jumppower = 0;
		}
		if ((arrow != null) && arrow.r < 1 && (player.posY < arrow.posY)) {
			jumppower = maxjump;
		}

		jumppower = this.getJumpPower(player, jumppower);
		
		double current_speed = GrappleConfig.getconf().rope_jump_at_angle ? -motion.dist_along(spherevec) : motion.y;
		if (current_speed > 0) {
			jumppower = jumppower - current_speed;
		}

		if (jumppower < 0) {jumppower = 0;}

		return jumppower;
	}

	public vec dampenmotion(vec motion, vec forward) {
		vec newmotion = motion.proj(forward);
		double dampening = 0.05;
		return new vec(newmotion.x*dampening + motion.x*(1-dampening), newmotion.y*dampening + motion.y*(1-dampening), newmotion.z*dampening + motion.z*(1-dampening));
	}
	
	public void updateServerPos() {
		grapplemod.network.sendToServer(new PlayerMovementMessage(this.entityId, this.entity.posX, this.entity.posY, this.entity.posZ, this.entity.motionX, this.entity.motionY, this.entity.motionZ));
	}
	
	// Vector stuff:
	
	public void receiveGrappleDetach() {
		this.unattach();
	}

	public void receiveEnderLaunch(double x, double y, double z) {
		this.motion.add_ip(x, y, z);
		this.entity.motionX = this.motion.x;
		this.entity.motionY = this.motion.y;
		this.entity.motionZ = this.motion.z;
	}
	
	public void applyAirFriction() {
		double dragforce = 1 / 200F;
		if (this.entity.isInWater() || this.entity.isInLava()) {
//			this.applyWaterFriction();
			dragforce = 1 / 4F;
		}
		
		double vel = this.motion.length();
		dragforce = vel * dragforce;
		
		vec airfric = new vec(this.motion.x, this.motion.y, this.motion.z);
		airfric.changelen_ip(-dragforce);
		this.motion.add_ip(airfric);
	}
	
/*	public void applyWaterFriction() {
		double vel = this.motion.length();
		double dragforce = vel*vel / 4;
		
		vec airfric = new vec(this.motion.x, this.motion.y, this.motion.z);
		airfric.changelen_ip(-dragforce);
		this.motion.add_ip(airfric);
	}*/
	
	public void applyPlayerMovement() {
		motion.add_ip(this.playermovement.changelen(0.015 + motion.length() * 0.01).mult(this.playermovementmult));//0.02 * playermovementmult));
	}

	public void addArrow(grappleArrow arrow) {
		this.arrows.add(arrow);
		arrow.r = ((grappleArrow) arrow).segmenthandler.getDist(vec.positionvec(arrow), vec.positionvec(entity).add(new vec(0, entity.getEyeHeight(), 0)));
		this.arrowIds.add(arrow.getEntityId());
	}
	
    public double repelmaxpush = 0.3;//0.25;
	
    // repel stuff
    public vec check_repel(vec p, World w) {
//    	long startTime = System.nanoTime();
    	
    	p = p.add(0.0, 0.75, 0.0);
    	vec v = new vec(0, 0, 0);
    	
    	/*
    	for (int x = (int)p.x - radius; x <= (int)p.x + radius; x++) {
        	for (int y = (int)p.y - radius; y <= (int)p.y + radius; y++) {
            	for (int z = (int)p.z - radius; z <= (int)p.z + radius; z++) {
			    	BlockPos pos = new BlockPos(x, y, z);
			    	if (pos != null) {
				    	if (hasblock(pos, w)) {
				    		vec blockvec = new vec(((double) x)+0.5, ((double) y)+0.5, ((double) z)+0.5);
				    		blockvec.sub_ip(p);
				    		blockvec.changelen_ip(-1 / Math.pow(blockvec.length(), 2));
				    		v.add_ip(blockvec);
				    	}
			    	}
            	}
	    	}
    	}
    	*/
    	
    	double t = (1.0 + Math.sqrt(5.0)) / 2.0;
    	
		BlockPos pos = new BlockPos(Math.floor(p.x), Math.floor(p.y), Math.floor(p.z));
		if (hasblock(pos, w)) {
			v.add_ip(0, 1, 0);
		} else {
	    	v.add_ip(vecdist(p, new vec(-1,  t,  0), w));
	    	v.add_ip(vecdist(p, new vec( 1,  t,  0), w));
	    	v.add_ip(vecdist(p, new vec(-1, -t,  0), w));
	    	v.add_ip(vecdist(p, new vec( 1, -t,  0), w));
	    	v.add_ip(vecdist(p, new vec( 0, -1,  t), w));
	    	v.add_ip(vecdist(p, new vec( 0,  1,  t), w));
	    	v.add_ip(vecdist(p, new vec( 0, -1, -t), w));
	    	v.add_ip(vecdist(p, new vec( 0,  1, -t), w));
	    	v.add_ip(vecdist(p, new vec( t,  0, -1), w));
	    	v.add_ip(vecdist(p, new vec( t,  0,  1), w));
	    	v.add_ip(vecdist(p, new vec(-t,  0, -1), w));
	    	v.add_ip(vecdist(p, new vec(-t,  0,  1), w));
		}
    	
    	if (v.length() > repelmaxpush) {
    		v.changelen_ip(repelmaxpush);
    	}
    	
//    	long endTime = System.nanoTime();

//    	long duration = (endTime - startTime);  //divide by 1000000 to get milliseconds.
//    	System.out.println(duration);
    	
		return v;
	}
    
    public vec vecdist(vec p, vec v, World w) {
    	for (double i = 0.5; i < 10; i += 0.5) {
    		vec v2 = v.changelen(i);
    		BlockPos pos = new BlockPos(Math.floor(p.x + v2.x), Math.floor(p.y + v2.y), Math.floor(p.z + v2.z));
    		if (hasblock(pos, w)) {
    			vec v3 = new vec(pos.getX() + 0.5 - p.x, pos.getY() + 0.5 - p.y, pos.getZ() + 0.5 - p.z);
    			v3.changelen_ip(-1 / Math.pow(v3.length(), 2));
    			return v3;
    		}
    	}
    	
    	return new vec(0, 0, 0);
    }
    
	public boolean hasblock(BlockPos pos, World w) {
//    	if (!blockcache.containsKey(pos)) {
    		boolean isblock = false;
	    	IBlockState blockstate = w.getBlockState(pos);
	    	Block b = blockstate.getBlock();
	    	if (!(b.isAir(blockstate, w, pos))) {
	    		isblock = true;
	    	}
			
//			blockcache.put(pos, (Boolean) isblock);
	    	return isblock;
//    	} else {
//    		return blockcache.get(pos);
//    	}
	}

	public void receiveGrappleDetachHook(int hookid) {
		if (this.arrowIds.contains(hookid)) {
			this.arrowIds.remove(hookid);
		} else {
			System.out.println("Error: controller received hook detach, but hook id not in arrowIds");
		}
		
		grappleArrow arrowToRemove = null;
		for (grappleArrow arrow : this.arrows) {
			if (arrow.getEntityId() == hookid) {
				arrowToRemove = arrow;
				break;
			}
		}
		
		if (arrowToRemove != null) {
			this.arrows.remove(arrowToRemove);
		} else {
			System.out.println("Error: controller received hook detach, but arrow not in arrows");
		}
	}
	
	public boolean rocket_key = false;
	public double rocket_on = 0;
	public vec rocket(Entity entity) {
		if (ClientProxyClass.key_rocket.isKeyDown()) {
			rocket_on = grapplemod.proxy.getRocketFunctioning();
			double rocket_force = this.custom.rocket_force * 0.225 * rocket_on;
        	double yaw = entity.rotationYaw;
        	double pitch = -entity.rotationPitch;
        	pitch += this.custom.rocket_vertical_angle;
        	vec force = new vec(0, 0, rocket_force);
        	force = force.rotate_pitch(Math.toRadians(pitch));
        	force = force.rotate_yaw(Math.toRadians(yaw));
        	
        	rocket_key = true;
        	return force;
		}
		rocket_key = false;
		rocket_on = 0F;
		return new vec(0,0,0);
	}
	
	boolean isonwall = false;
	vec walldirection = null;
	RayTraceResult wallrun_raytrace_result = null;
//	boolean wallonleft = true;
	
	public vec getnearbywall(vec tryfirst, vec trysecond, double extra) {
		float entitywidth = this.entity.width;
		
		for (vec direction : new vec[] {tryfirst, trysecond, tryfirst.mult(-1), trysecond.mult(-1)}) {
			RayTraceResult raytraceresult = this.entity.world.rayTraceBlocks(this.entity.getPositionVector(), vec.positionvec(this.entity).add(direction.changelen(entitywidth/2 + extra)).toVec3d(), false, true, false);
			if (raytraceresult != null && raytraceresult.typeOfHit == RayTraceResult.Type.BLOCK) {
				wallrun_raytrace_result = raytraceresult;
				return direction;
			}
		}
		
		return null;
	}
	
	public vec getwalldirection() {
		vec tryfirst = new vec(0, 0, 0);
		vec trysecond = new vec(0, 0, 0);
		
		if (Math.abs(this.motion.x) > Math.abs(this.motion.z)) {
			tryfirst.x = (this.motion.x > 0) ? 1 : -1;
			trysecond.z = (this.motion.z > 0) ? 1 : -1;
		} else {
			tryfirst.z = (this.motion.z > 0) ? 1 : -1;
			trysecond.x = (this.motion.x > 0) ? 1 : -1;
		}
		
		return getnearbywall(tryfirst, trysecond, 0.05);
	}
//	
//	public vec getclosebywall() {
//		if (this.walldirection != null) {
//			vec tryfirst = this.walldirection;
//			vec trysecond = new vec(0,0,0);
//			if (tryfirst.x == 0) {
//				trysecond.x = 1;
//			} else {
//				trysecond.z = 1;
//			}
//			
//			vec walldir = getnearbywall(tryfirst, trysecond, 1.05);
//			if (walldir != null && walldir.dot(this.motion) >= 0) {
//				return walldir;
//			}
//		}
//		
//		return null;
//	}
	
	public vec getcorner(int cornernum, vec facing, vec sideways) {
		vec corner = new vec(0,0,0);
		if (cornernum / 2 == 0) {
			corner.add_ip(facing);
		} else {
			corner.add_ip(facing.mult(-1));
		}
		if (cornernum % 2 == 0) {
			corner.add_ip(sideways);
		} else {
			corner.add_ip(sideways.mult(-1));
		}
		return corner;
	}
	
	public boolean wallnearby(double dist) {
//		double boxsize = 2;
		
//		// facing 2d
//		vec facing = new vec(this.entity.getLookVec());
//		facing.y = 0;
//		if (facing.length() <= 0.01) {
//			facing = new vec(1, 0, 0);
//		}
//		facing.changelen_ip(boxsize);
//		
//		vec sideways = facing.cross(new vec(0,1,0));
//		sideways.changelen_ip(boxsize);
		
		float entitywidth = this.entity.width;
		vec v1 = new vec(entitywidth/2 + dist, 0, 0);
		vec v2 = new vec(0, 0, entitywidth/2 + dist);
		
		for (int i = 0; i < 4; i++) {
			vec corner1 = getcorner(i, v1, v2);
			vec corner2 = getcorner((i + 1) % 4, v1, v2);
			
			RayTraceResult raytraceresult = this.entity.world.rayTraceBlocks(vec.positionvec(this.entity).add(corner1).toVec3d(), vec.positionvec(this.entity).add(corner2).toVec3d(), false, true, false);
			if (raytraceresult != null && raytraceresult.typeOfHit == RayTraceResult.Type.BLOCK) {
				return true;
			}
		}
		
		return false;
	}
	
	int ticks_since_last_wallrun_sound_effect = 0;
	
	public boolean iswallrunning() {
		double current_speed = Math.sqrt(Math.pow(this.motion.x, 2) + Math.pow(this.motion.z,  2));
		if (current_speed < GrappleConfig.getconf().wallrun_min_speed) {
			isonwall = false;
			return false;
		}
		
		if (isonwall) {
			grapplemod.proxy.setWallrunTicks(grapplemod.proxy.getWallrunTicks()+1);
		}
		
		if (grapplemod.proxy.getWallrunTicks() < GrappleConfig.getconf().max_wallrun_time * 40) {
			if (!(playersneak)) {
				// continue wallrun
				if (isonwall && !this.entity.onGround && this.entity.collidedHorizontally) {
					if (entity instanceof EntityLivingBase && ((EntityLivingBase) entity).isOnLadder()) {
						return false;
					}
					return true;
				}
				
				// start wallrun
				if (grapplemod.proxy.iswallrunning(this.entity, this.motion)) {
					isonwall = true;
					return true;
				}
			}
			
			isonwall = false;
		}
		
		if (grapplemod.proxy.getWallrunTicks() > 0 && (this.entity.onGround || (!this.entity.collidedHorizontally && !wallnearby(0.2)))) {
			ticks_since_last_wallrun_sound_effect = 0;
		}
		
		return false;
	}
	
	public boolean applywallrun() {
		boolean wallrun = this.iswallrunning();
		
		if (playerjump) {
			if (wallrun) {
				return false;
			} else {
				playerjump = false;
			}
		}
		
		if (wallrun && !ClientProxyClass.key_jumpanddetach.isKeyDown()) {

			vec wallside = this.getwalldirection();
			if (wallside != null) {
				this.walldirection = wallside;
			}
			
			if (this.walldirection == null) {
				return false;
			}

			if (!playerjump) {
				motion.y = 0;
			}

			// drag
			double dragforce = GrappleConfig.getconf().wallrun_drag;
			
			double vel = this.motion.length();
//			dragforce = vel*vel * dragforce;
			
			if (dragforce > vel) {dragforce = vel;}
			
			vec wallfric = new vec(this.motion);
			if (wallside != null) {
				wallfric.removealong(wallside);
			}
			wallfric.changelen_ip(-dragforce);
			this.motion.add_ip(wallfric);

			ticks_since_last_wallrun_sound_effect++;
			if (ticks_since_last_wallrun_sound_effect > GrappleConfig.client_options.wallrun_sound_effect_time_s * 20 * GrappleConfig.getconf().wallrun_max_speed / (vel + 0.00000001)) {
				if (wallrun_raytrace_result != null && wallrun_raytrace_result.typeOfHit == RayTraceResult.Type.BLOCK) {
					BlockPos blockpos = wallrun_raytrace_result.getBlockPos();
					
					IBlockState blockState = this.entity.world.getBlockState(blockpos);
					Block blockIn = blockState.getBlock();
					
			        SoundType soundtype = blockIn.getSoundType(blockState, world, blockpos, this.entity);

			        if (!blockIn.getDefaultState().getMaterial().isLiquid())
			        {
			            this.entity.playSound(soundtype.getStepSound(), soundtype.getVolume() * 0.30F * GrappleConfig.client_options.wallrun_sound_volume, soundtype.getPitch());
						ticks_since_last_wallrun_sound_effect = 0;
			        }
				}
			}
		}
		
		// jump
		boolean isjumping = ClientProxyClass.key_jumpanddetach.isKeyDown() && isonwall;
		isjumping = isjumping && !playerjump; // only jump once when key is first pressed
		playerjump = ClientProxyClass.key_jumpanddetach.isKeyDown() && isonwall;
		if (isjumping && wallrun) {
			grapplemod.proxy.setWallrunTicks(0);
			vec jump = new vec(0, GrappleConfig.getconf().wall_jump_up, 0);
			if (walldirection != null) {
				jump.add_ip(walldirection.mult(-GrappleConfig.getconf().wall_jump_side));
			}
			motion.add_ip(jump);
			
			wallrun = false;

			grapplemod.proxy.playWallrunJumpSound(entity);
		}
		
		return wallrun;
	}
	
	public vec wallrun_press_against_wall() {
		// press against wall
		if (this.walldirection != null) {
			return this.walldirection.changelen(0.05);
		}
		return new vec(0,0,0);
	}

	public void doublejump() {
		if (-this.motion.y > GrappleConfig.getconf().dont_doublejump_if_falling_faster_than) {
			return;
		}
		if (this.motion.y < 0 && !GrappleConfig.getconf().doublejump_relative_to_falling) {
			this.motion.y = 0;
		}
		this.motion.y += GrappleConfig.getconf().doublejumpforce;
		entity.motionX = motion.x;
		entity.motionY = motion.y;
		entity.motionZ = motion.z;
	}
	
	public void applySlidingFriction() {
		double dragforce = GrappleConfig.getconf().sliding_friction;
		
//		double vel = this.motion.length();
//		dragforce = vel*vel * dragforce;
		
		if (dragforce > this.motion.length()) {dragforce = this.motion.length(); };
		
		vec airfric = new vec(this.motion.x, this.motion.y, this.motion.z);
		airfric.changelen_ip(-dragforce);
		this.motion.add_ip(airfric);
	}

	public void slidingJump() {
		this.motion.y = GrappleConfig.getconf().slidingjumpforce;
	}
} public class repelController extends grappleController {
	public repelController(int arrowId, int entityId, World world, vec pos, int id) {
		super(arrowId, entityId, world, pos, id, null);
		
		this.playermovementmult = 1;
	}

	public void updatePlayerPos() {
		Entity entity = this.entity;
		
		if (this.attached) {
			if(entity != null) {
				if (true) {
					this.normalGround(false);
					this.normalCollisions(false);
//					this.applyAirFriction();
					
					vec playerpos = vec.positionvec(entity);
					
//					double dist = oldspherevec.length();
					
					if (entity instanceof EntityPlayer) {
//						EntityPlayer player = (EntityPlayer) entity;
						if (playersneak) {
							motion.mult_ip(0.95);
						}
						applyPlayerMovement();
					}
					
					vec blockpush = check_repel(playerpos, entity.world);
					blockpush.mult_ip(0.5);
					blockpush = new vec(blockpush.x*0.5, blockpush.y*2, blockpush.z*0.5);
					this.motion.add_ip(blockpush);
					
					if (!entity.onGround) {
						motion.add_ip(0, -0.05, 0);
					}
					
					entity.motionX = motion.x;
					entity.motionY = motion.y;
					entity.motionZ = motion.z;
					
					this.updateServerPos();
				}
			}
		}
	}} public class DoublejumpEnchantment extends Enchantment {
	public DoublejumpEnchantment() {
		super(grapplemod.getRarityFromInt(GrappleConfig.getconf().enchant_rarity_double_jump), grapplemod.GRAPPLEENCHANTS_FEET, new EntityEquipmentSlot[] {EntityEquipmentSlot.FEET});
		this.setName("doublejumpenchantment");
	}
    public int getMinEnchantability(int enchantmentLevel)
    {
        return 1;
    }

    public int getMaxEnchantability(int enchantmentLevel)
    {
        return this.getMinEnchantability(enchantmentLevel) + 40;
    }

    public int getMaxLevel()
    {
        return 1;
    }
} public class SlidingEnchantment extends Enchantment {
	public SlidingEnchantment() {
		super(grapplemod.getRarityFromInt(GrappleConfig.getconf().enchant_rarity_sliding), grapplemod.GRAPPLEENCHANTS_FEET, new EntityEquipmentSlot[] {EntityEquipmentSlot.FEET});
		this.setName("slidingenchantment");
	}
    public int getMinEnchantability(int enchantmentLevel)
    {
        return 1;
    }

    public int getMaxEnchantability(int enchantmentLevel)
    {
        return this.getMinEnchantability(enchantmentLevel) + 40;
    }

    public int getMaxLevel()
    {
        return 1;
    }
} public class WallrunEnchantment extends Enchantment {
	public WallrunEnchantment() {
		super(grapplemod.getRarityFromInt(GrappleConfig.getconf().enchant_rarity_wallrun), grapplemod.GRAPPLEENCHANTS_FEET, new EntityEquipmentSlot[] {EntityEquipmentSlot.FEET});
		this.setName("wallrunenchantment");
	}
    public int getMinEnchantability(int enchantmentLevel)
    {
        return 1;
    }

    public int getMaxEnchantability(int enchantmentLevel)
    {
        return this.getMinEnchantability(enchantmentLevel) + 40;
    }

    public int getMaxLevel()
    {
        return 1;
    }
} public class grappleArrow extends EntityThrowable implements IEntityAdditionalSpawnData
{
	public Entity shootingEntity = null;
	public int shootingEntityID;
	
	private boolean firstattach = false;
	public vec thispos;
	
	public boolean righthand = true;
	
	public boolean attached = false;
	
	public double pull;
	
	public double taut = 1;
	
	public boolean ignoreFrustumCheck = true;
	
	public boolean isdouble = false;
	
//	public double maxlen = 20;
	public double r;
	
	public SegmentHandler segmenthandler = null;
	
	public GrappleCustomization customization = null;
	
/*	public vec debugpos = null;
	public vec debugpos2 = null;
	public vec debugpos3 = null;*/
	
	// magnet attract
	public vec prevpos = null;
	public boolean foundblock = false;
	public boolean wasinair = false;
	public BlockPos magnetblock = null;
	
	public grappleArrow(World worldIn) {
		super(worldIn);
		
		this.segmenthandler = new SegmentHandler(this.world, this, vec.positionvec(this), vec.positionvec(this));
		this.customization = new GrappleCustomization();
	}
	
	public grappleArrow(World worldIn, EntityLivingBase shooter,
			boolean righthand, GrappleCustomization customization, boolean isdouble) {
		super(worldIn, shooter);
		
		this.shootingEntity = shooter;
		this.shootingEntityID = this.shootingEntity.getEntityId();
		
		this.isdouble = isdouble;
		
		/*
		double x = 0.36;
		if (righthand) {x = -0.36;}
        vec pos = vec.positionvec(this);
        pos.add_ip(new vec(x, -0.175, 0.45).rotate_yaw(Math.toRadians(shooter.rotationYaw)));
        this.setPosition(pos.x, pos.y, pos.z);
        */
		
		vec pos = vec.positionvec(this.shootingEntity).add(new vec(0, this.shootingEntity.getEyeHeight(), 0));

		this.segmenthandler = new SegmentHandler(this.world, this, new vec(pos), new vec(pos));

		this.customization = customization;
		this.r = customization.maxlen;
		
		this.righthand = righthand;
	}
	
	@Override
	public void onUpdate(){
		super.onUpdate();
		
		if (this.shootingEntityID == 0 || this.shootingEntity == null) { // removes ghost grappling hooks
			this.remove();
		}
		
		if (this.firstattach) {
			this.motionX = 0;
			this.motionY = 0;
			this.motionZ = 0;
			this.firstattach = false;
			super.setPosition(this.thispos.x, this.thispos.y, this.thispos.z);
		}
		
		if (!this.world.isRemote) {
			if (this.shootingEntity != null)  {
				if (!this.attached) {
					if (this.segmenthandler.hookpastbend(this.r)) {
						System.out.println("around bend");
						vec farthest = this.segmenthandler.getfarthest();
						this.serverAttach(this.segmenthandler.getbendblock(1), farthest, null);
					}
					
					if (!this.customization.phaserope) {
						this.segmenthandler.update(vec.positionvec(this), vec.positionvec(this.shootingEntity).add(new vec(0, this.shootingEntity.getEyeHeight(), 0)), this.r, true);
						
						if (this.customization.sticky) {
							if (this.segmenthandler.segments.size() > 2) {
								int bendnumber = this.segmenthandler.segments.size() - 2;
								vec closest = this.segmenthandler.segments.get(bendnumber);
								BlockPos blockpos = this.segmenthandler.getbendblock(bendnumber);
								for (int i = 1; i <= bendnumber; i++) { 
									this.segmenthandler.removesegment(1);
								}
								this.serverAttach(blockpos, closest, null);
							}
						}
					} else {
						this.segmenthandler.updatepos(vec.positionvec(this), vec.positionvec(this.shootingEntity).add(new vec(0, this.shootingEntity.getEyeHeight(), 0)), this.r);
					}
					
					vec farthest = this.segmenthandler.getfarthest();
					double distToFarthest = this.segmenthandler.getDistToFarthest();
					
					vec ropevec = vec.positionvec(this).sub(farthest);
					double d = ropevec.length();
					
					if (this.customization.reelin && this.shootingEntity.isSneaking()) {
						double newdist = d + distToFarthest - 0.4;
						if (newdist > 1 && newdist <= this.customization.maxlen) {
							this.r = newdist;
						}
					}


					if (d + distToFarthest > this.r) {
						vec motion = vec.motionvec(this);
						
						if (motion.dot(ropevec) > 0) {
							motion = motion.removealong(ropevec);
						}
						
						this.setVelocityActually(motion.x, motion.y, motion.z);
						
						ropevec.changelen_ip(this.r - distToFarthest);
						vec newpos = ropevec.add(farthest);
						
						this.setPosition(newpos.x, newpos.y, newpos.z);
					}
					
				}
			}
/*		} else {
			vec farthest = this.segmenthandler.getfarthest();
			double distToFarthest = this.segmenthandler.getDistToFarthest();
			
			vec ropevec = vec.positionvec(this).sub(farthest);
			double d = ropevec.length();
			
			this.taut = d + distToFarthest / maxlen;*/
		}
		
		// magnet attraction
		if (this.customization.attract && vec.positionvec(this).sub(vec.positionvec(this.shootingEntity)).length() > this.customization.attractradius) {
	    	if (this.shootingEntity == null) {return;}
	    	if (!this.foundblock) {
	    		if (!this.world.isRemote) {
	    			vec playerpos = vec.positionvec(this.shootingEntity);
	    			vec pos = vec.positionvec(this);
	    			if (magnetblock == null) {
		    			if (prevpos != null) {
			    			HashMap<BlockPos, Boolean> checkedset = new HashMap<BlockPos, Boolean>();
			    			vec vector = pos.sub(prevpos);
			    			vec normvector = vector.normalize();
			    			for (int i = 0; i < vector.length(); i++) {
			    				double dist = prevpos.sub(playerpos).length();
			    				int radius = (int) dist / 4;
			    				BlockPos found = this.check(prevpos, checkedset);
			    				if (found != null) {
//			    					if (wasinair) {
							    		vec distvec = new vec(found.getX(), found.getY(), found.getZ());
							    		distvec.sub_ip(prevpos);
							    		if (distvec.length() < radius) {
					    					this.setPositionAndUpdate(prevpos.x, prevpos.y, prevpos.z);
					    					pos = prevpos;
					    					
					    					magnetblock = found;
					    					
					    					break;
							    		}
//			    					}
			    				} else {
			    					wasinair = true;
			    				}
			    				
			    				prevpos.add_ip(normvector);
			    			}
		    			}
	    			}
	    			
	    			if (magnetblock != null) {
				    	IBlockState blockstate = this.world.getBlockState(magnetblock);
				    	AxisAlignedBB BB = blockstate.getCollisionBoundingBox(this.world, magnetblock);

						vec blockvec = new vec(magnetblock.getX() + (BB.maxX + BB.minX) / 2, magnetblock.getY() + (BB.maxY + BB.minY) / 2, magnetblock.getZ() + (BB.maxZ + BB.minZ) / 2);
						vec newvel = blockvec.sub(pos);
						
						double l = newvel.length();
						
						newvel.changelen(this.getVelocity());
						
						this.motionX = newvel.x;
						this.motionY = newvel.y;
						this.motionZ = newvel.z;
						
						if (l < 0.2) {
							this.serverAttach(magnetblock, blockvec, EnumFacing.UP);
						}
	    			}
	    			
	    			prevpos = pos;
	    		}
	    	}
		}
	}
	
	public void setVelocityActually(double x, double y, double z) {
        this.motionX = x;
        this.motionY = y;
        this.motionZ = z;

        if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
        {
            float f = MathHelper.sqrt(x * x + z * z);
            this.rotationYaw = (float)(MathHelper.atan2(x, z) * (180D / Math.PI));
            this.rotationPitch = (float)(MathHelper.atan2(y, (double)f) * (180D / Math.PI));
            this.prevRotationYaw = this.rotationYaw;
            this.prevRotationPitch = this.rotationPitch;
        }
	}
	
	@Override
	@SideOnly(Side.CLIENT)
	public boolean isInRangeToRender3d(double x, double y, double z) {
		return true;
	}

	@Override
	@SideOnly(Side.CLIENT)
	public boolean isInRangeToRenderDist(double distance) {
		return true;
	}

	public final int RenderBoundingBoxSize = 999;
	@Override
	@SideOnly(Side.CLIENT)
	public AxisAlignedBB getRenderBoundingBox() {
		if (this.shootingEntity == null) {
			return super.getRenderBoundingBox();
		}
		
		AxisAlignedBB bb = this.segmenthandler.getBoundingBox(vec.positionvec(this), vec.positionvec(this.shootingEntity).add(new vec(0, this.shootingEntity.getEyeHeight(), 0)));
//		bb = bb.union(this.shootingEntity.getEntityBoundingBox());
//		AxisAlignedBB shooting_bb = this.shootingEntity.getRenderBoundingBox();
//		bb = bb.expand(shooting_bb.minX, shooting_bb.minY, shooting_bb.minZ);
//		bb = bb.expand(shooting_bb.maxX, shooting_bb.maxY, shooting_bb.maxZ);
//		System.out.println(bb);
//		bb.grow(1);
		return bb;
//		return new AxisAlignedBB(this.posX - RenderBoundingBoxSize, this.posY - RenderBoundingBoxSize, this.posZ - RenderBoundingBoxSize, 
//				this.posX + RenderBoundingBoxSize, this.posY + RenderBoundingBoxSize, this.posZ + RenderBoundingBoxSize);
	}

//	@Override
//	public AxisAlignedBB getEntityBoundingBox() {
//		return this.segmenthandler.getBoundingBox(vec.positionvec(this), vec.positionvec(this.shootingEntity).add(new vec(0, this.shootingEntity.getEyeHeight(), 0)));
//	}
	
/*	public boolean toofaraway() {
    	if (this.shootingEntity == null) {return false;}
		if (!this.world.isRemote) {
			if (!grapplemod.attached.contains(this.shootingEntityID)) {
				if (grapplemod.grapplingLength != 0) {
					double d = vec.positionvec(this).sub(vec.positionvec(this.shootingEntity)).length();
					if (d > grapplemod.grapplingLength) {
						return true;
					}
				}
			}
		}
		return false;
	}*/

	public void setPosition(double x, double y, double z) {
		if (this.thispos != null) {
			x = this.thispos.x;
			y = this.thispos.y;
			z = this.thispos.z;
		}
		super.setPosition(x, y, z);
	}
	
	
	@Override
    public void writeSpawnData(ByteBuf data)
    {
	    data.writeInt(this.shootingEntity != null ? this.shootingEntity.getEntityId() : 0);
	    data.writeBoolean(this.righthand);
	    data.writeBoolean(this.isdouble);
	    if (this.customization == null) {
	    	System.out.println("error: customization null");
	    }
	    this.customization.writeToBuf(data);
    }
	
	@Override
    public void readSpawnData(ByteBuf data)
    {
    	this.shootingEntityID = data.readInt();
	    this.shootingEntity = this.world.getEntityByID(this.shootingEntityID);
	    this.righthand = data.readBoolean();
	    this.isdouble = data.readBoolean();
	    this.customization = new GrappleCustomization();
	    this.customization.readFromBuf(data);
    }
	
	public void remove() {
		this.setDead();
	}
	
	@Override
	public String toString() {
		return super.toString() + String.valueOf(System.identityHashCode(this)) + "]";
	}

	@Override
	protected void onImpact(RayTraceResult movingobjectposition) {
		if (!this.world.isRemote) {
			if (this.attached) {
				return;
			}
			if (this.shootingEntityID != 0) {
				if (movingobjectposition == null) {
					return;
				}
				
				Vec3d vec3d = new Vec3d(this.posX, this.posY, this.posZ);
		        Vec3d vec3d1 = new Vec3d(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);

				if (movingobjectposition.typeOfHit == RayTraceResult.Type.ENTITY && !GrappleConfig.getconf().hookaffectsentities) {
			        onImpact(this.world.rayTraceBlocks(vec3d, vec3d1, false, true, false));
			        return;
				}
				
				if (grapplemod.anyignoresblocks && movingobjectposition.typeOfHit == RayTraceResult.Type.BLOCK) {
					BlockPos blockpos = movingobjectposition.getBlockPos();
					if (blockpos != null) {
						Block block = this.world.getBlockState(blockpos).getBlock();
						if (grapplemod.grapplingignoresblocks.contains(block)) {
							System.out.println("ignore block");
					        onImpact(this.world.rayTraceBlocks(vec3d, vec3d1, false, true, false));
					        return;
						}
					}
				}
				
				if (grapplemod.anybreakblocks && movingobjectposition.typeOfHit == RayTraceResult.Type.BLOCK) {
					BlockPos blockpos = movingobjectposition.getBlockPos();
					if (blockpos != null) {
						Block block = this.world.getBlockState(blockpos).getBlock();
						if (grapplemod.grapplingbreaksblocks.contains(block)) {
							this.world.destroyBlock(blockpos, true);
							System.out.println("break block");
					        onImpact(this.world.rayTraceBlocks(vec3d, vec3d1, false, true, false));
					        return;
						}
					}
				}
				
				if (movingobjectposition.typeOfHit == RayTraceResult.Type.ENTITY) {
					// hit entity
					Entity entityhit = movingobjectposition.entityHit;
					if (entityhit == this.shootingEntity) {
						return;
					}
					
					vec playerpos = vec.positionvec(this.shootingEntity);
					vec entitypos = vec.positionvec(entityhit);
					vec yank = playerpos.sub(entitypos).mult(0.4);
					entityhit.addVelocity(yank.x, Math.min(yank.y, 2), yank.z);
					
					this.removeServer();
					return;
				} else if (movingobjectposition.typeOfHit == RayTraceResult.Type.BLOCK) {
					BlockPos blockpos = movingobjectposition.getBlockPos();
					
					vec vec3 = new vec(movingobjectposition.hitVec.x, movingobjectposition.hitVec.y, movingobjectposition.hitVec.z);

					this.serverAttach(blockpos, vec3, movingobjectposition.sideHit);
				} else {
					System.out.println("unknown impact?");
				}
			}
		}
	}
	
	public void serverAttach(BlockPos blockpos, vec pos, EnumFacing sideHit) {
		if (this.attached) {
			return;
		}
		this.attached = true;

		if (blockpos != null) {
			if (!grapplemod.anyblocks) {
				Block block = this.world.getBlockState(blockpos).getBlock();

				if ((!grapplemod.removeblocks && !grapplemod.grapplingblocks.contains(block))
						|| (grapplemod.removeblocks && grapplemod.grapplingblocks.contains(block))) {
					this.removeServer();
					return;
				}
			}
		}
		
		vec vec3 = vec.positionvec(this);
		vec3.add_ip(vec.motionvec(this));
		if (pos != null) {
            vec3 = pos;
            
            this.setPositionAndUpdate(vec3.x, vec3.y, vec3.z);
		}
		
		//west -x
		//north -z
		if (sideHit == EnumFacing.DOWN) {
			this.posY -= 0.3;
		} else if (sideHit == EnumFacing.WEST) {
			this.posX -= 0.05;
		} else if (sideHit == EnumFacing.NORTH) {
			this.posZ -= 0.05;
		} else if (sideHit == EnumFacing.SOUTH) {
			this.posZ += 0.05;
		} else if (sideHit == EnumFacing.EAST) {
			this.posX += 0.05;
		} else if (sideHit == EnumFacing.UP) {
			this.posY += 0.05;
		}
		
        this.motionX = 0;
        this.motionY = 0;
        this.motionZ = 0;
        
        this.thispos = vec.positionvec(this);
		this.firstattach = true;
		grapplemod.attached.add(this.shootingEntityID);
		
		grapplemod.sendtocorrectclient(new GrappleAttachMessage(this.getEntityId(), this.posX, this.posY, this.posZ, this.getControlId(), this.shootingEntityID, blockpos, this.segmenthandler.segments, this.segmenthandler.segmenttopsides, this.segmenthandler.segmentbottomsides, this.customization), this.shootingEntityID, this.world);
		if (this.shootingEntity instanceof EntityPlayerMP) { // fixes strange bug in LAN
			EntityPlayerMP sender = (EntityPlayerMP) this.shootingEntity;
			int dimension = sender.dimension;
			MinecraftServer minecraftServer = sender.mcServer;
			for (EntityPlayerMP player : minecraftServer.getPlayerList().getPlayers()) {
				GrappleAttachPosMessage msg = new GrappleAttachPosMessage(this.getEntityId(), this.posX, this.posY, this.posZ);   // must generate a fresh message for every player!
				if (dimension == player.dimension) {
					grapplemod.sendtocorrectclient(msg, player.getEntityId(), player.world);
				}
			}
		}
	}
	
	public void clientAttach(double x, double y, double z) {
		this.setAttachPos(x, y, z);
		
		if (this.shootingEntity instanceof EntityPlayer) {
			grapplemod.proxy.resetlaunchertime(this.shootingEntityID);
		}
	}
	
	
	@Override
    protected float getGravityVelocity()
    {
        return (float) this.customization.hookgravity * 0.1F;
    }
	
    public float getVelocity()
    {
        return (float) this.customization.throwspeed;
    }

	public void removeServer() {
		this.setDead();
		this.shootingEntityID = 0;

	}
	
	public int getControlId() {
		return grapplemod.GRAPPLEID;
	}

	public void setAttachPos(double x, double y, double z) {
		this.setPositionAndUpdate(x, y, z);

		this.motionX = 0;
		this.motionY = 0;
		this.motionZ = 0;
		this.firstattach = true;
		this.attached = true;
        this.thispos = new vec(x, y, z);
	}
	
	// used for magnet attraction
    public BlockPos check(vec p, HashMap<BlockPos, Boolean> checkedset) {
    	int radius = (int) Math.floor(this.customization.attractradius);
    	BlockPos closestpos = null;
    	double closestdist = 0;
    	for (int x = (int)p.x - radius; x <= (int)p.x + radius; x++) {
        	for (int y = (int)p.y - radius; y <= (int)p.y + radius; y++) {
            	for (int z = (int)p.z - radius; z <= (int)p.z + radius; z++) {
			    	BlockPos pos = new BlockPos(x, y, z);
			    	if (pos != null) {
				    	if (hasblock(pos, checkedset)) {
				    		vec distvec = new vec(pos.getX(), pos.getY(), pos.getZ());
				    		distvec.sub_ip(p);
				    		double dist = distvec.length();
				    		if (closestpos == null || dist < closestdist) {
				    			closestpos = pos;
				    			closestdist = dist;
				    		}
				    	}
			    	}
            	}
	    	}
    	}
		return closestpos;
	}

	// used for magnet attraction
	public boolean hasblock(BlockPos pos, HashMap<BlockPos, Boolean> checkedset) {
    	if (!checkedset.containsKey(pos)) {
    		boolean isblock = false;
	    	IBlockState blockstate = this.world.getBlockState(pos);
	    	Block b = blockstate.getBlock();
			if (!grapplemod.anyblocks && ((!grapplemod.removeblocks && !grapplemod.grapplingblocks.contains(b))
						|| (grapplemod.removeblocks && grapplemod.grapplingblocks.contains(b)))) {
			} else {
		    	if (!(b.isAir(blockstate, this.world, pos))) {
			    	AxisAlignedBB BB = blockstate.getCollisionBoundingBox(this.world, pos);
			    	if (BB != null) {
			    		isblock = true;
			    	}
		    	}
			}
			
	    	checkedset.put(pos, (Boolean) isblock);
	    	return isblock;
    	} else {
    		return checkedset.get(pos);
    	}
	}
} public interface KeypressItem {
	enum Keys {
		LAUNCHER, THROWLEFT, THROWRIGHT, THROWBOTH, ROCKET
	}
	
	public abstract void onCustomKeyDown(ItemStack stack, EntityPlayer player, Keys key, boolean ismainhand);
	public abstract void onCustomKeyUp(ItemStack stack, EntityPlayer player, Keys key, boolean ismainhand);
} enum Keys {
	LAUNCHER, THROWLEFT, THROWRIGHT, THROWBOTH, ROCKET
} public class LongFallBoots extends ItemArmor {
	public LongFallBoots(ArmorMaterial material, int type) {
	    super(material, 0, EntityEquipmentSlot.FEET);
	    this.setUnlocalizedName("longfallboots");
	    MinecraftForge.EVENT_BUS.register(this);
		setCreativeTab(grapplemod.tabGrapplemod);
	}
	
	@SubscribeEvent
	public void onLivingHurtEvent(LivingHurtEvent event) {
		if (event.getEntity() != null && event.getEntity() instanceof EntityPlayer)
		{
			EntityPlayer player = (EntityPlayer)event.getEntity();
			
			for (ItemStack armor : player.getArmorInventoryList()) {
			    if (armor != null && armor.getItem() instanceof LongFallBoots)
			    {
			    	if (event.getSource() == DamageSource.FLY_INTO_WALL) {
			    		System.out.println("Flew into wall");
						// this cancels the fall event so you take no damage
						event.setCanceled(true);
			    	}
			    }
			}
		}
	}
	
	@SubscribeEvent
	public void onLivingFallEvent(LivingFallEvent event)
	{
		if (event.getEntity() != null && event.getEntity() instanceof EntityPlayer)
		{
			EntityPlayer player = (EntityPlayer)event.getEntity();
			
			for (ItemStack armor : player.getArmorInventoryList()) {
			    if (armor != null && armor.getItem() instanceof LongFallBoots)
			    {
					// this cancels the fall event so you take no damage
					event.setCanceled(true);
			    }
			}
		}
	}
	
	@Override
    @SideOnly(Side.CLIENT)
	public void addInformation(ItemStack stack, World world, List<String> list, ITooltipFlag par4)
	{
		if (!stack.isItemEnchanted()) {
			if (GrappleConfig.getconf().longfallbootsrecipe) {
				list.add("Right click a Grappling Hook Modifier block with Feather Falling IV Diamond Boots to obtain");
			}
		}
		list.add("Cancels fall damage when worn");
	}

	@Override
    public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items)
    {
        if (this.isInCreativeTab(tab))
        {
        	ItemStack stack = new ItemStack(this);
            items.add(stack);
            
        	stack = new ItemStack(this);
        	stack.addEnchantment(grapplemod.wallrunenchantment, 1);
        	stack.addEnchantment(grapplemod.doublejumpenchantment, 1);
        	stack.addEnchantment(grapplemod.slidingenchantment, 1);
            items.add(stack);
        }
    }
} public class grappleBow extends Item implements KeypressItem {
	public static HashMap<Entity, grappleArrow> grapplearrows1 = new HashMap<Entity, grappleArrow>();
	public static HashMap<Entity, grappleArrow> grapplearrows2 = new HashMap<Entity, grappleArrow>();
	
	public grappleBow() {
		super();
		maxStackSize = 1;
		setFull3D();
		setUnlocalizedName("grapplinghook");
		
		this.setMaxDamage(GrappleConfig.getconf().default_durability);
		
		setCreativeTab(grapplemod.tabGrapplemod);
		
		MinecraftForge.EVENT_BUS.register(this);
	}

    @Override
	public int getMaxItemUseDuration(ItemStack par1ItemStack)
	{
		return 72000;
	}
	
	public boolean hasArrow(Entity entity) {
		grappleArrow arrow1 = getArrowLeft(entity);
		grappleArrow arrow2 = getArrowRight(entity);
		return (arrow1 != null) || (arrow2 != null);
	}
	
	@Override
	public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) {
        ItemStack mat = new ItemStack(Items.LEATHER, 1);
        if (mat != null && net.minecraftforge.oredict.OreDictionary.itemMatches(mat, repair, false)) return true;
        return super.getIsRepairable(toRepair, repair);
	}

	public void setArrowLeft(Entity entity, grappleArrow arrow) {
		grappleBow.grapplearrows1.put(entity, arrow);
	}
	public void setArrowRight(Entity entity, grappleArrow arrow) {
		grappleBow.grapplearrows2.put(entity, arrow);
	}
	public grappleArrow getArrowLeft(Entity entity) {
		if (grappleBow.grapplearrows1.containsKey(entity)) {
			grappleArrow arrow = grappleBow.grapplearrows1.get(entity);
			if (arrow != null && !arrow.isDead) {
				return arrow;
			}
		}
		return null;
	}
	public grappleArrow getArrowRight(Entity entity) {
		if (grappleBow.grapplearrows2.containsKey(entity)) {
			grappleArrow arrow = grappleBow.grapplearrows2.get(entity);
			if (arrow != null && !arrow.isDead) {
				return arrow;
			}
		}
		return null;
	}	
	
	public void dorightclick(ItemStack stack, World worldIn, EntityLivingBase entityLiving, boolean righthand) {
        if (!worldIn.isRemote) {
    	}
	}
	
	public void throwBoth(ItemStack stack, World worldIn, EntityLivingBase entityLiving, boolean righthand) {
		grappleArrow arrow_left = getArrowLeft(entityLiving);
		grappleArrow arrow_right = getArrowRight(entityLiving);

		if (arrow_left != null || arrow_right != null) {
			detachBoth(entityLiving);
    		return;
		}
		
    	GrappleCustomization custom = this.getCustomization(stack);
  		double angle = custom.angle;
  		double verticalangle = custom.verticalthrowangle;
  		if (entityLiving.isSneaking()) {
  			angle = custom.sneakingangle;
  			verticalangle = custom.sneakingverticalthrowangle;
  		}

    	if (!(!custom.doublehook || angle == 0)) {
    		throwLeft(stack, worldIn, entityLiving, righthand);
    	}
		throwRight(stack, worldIn, entityLiving, righthand);

		stack.damageItem(1, entityLiving);
        worldIn.playSound((EntityPlayer)null, entityLiving.posX, entityLiving.posY, entityLiving.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + 2.0F * 0.5F);
	}
	
	public boolean throwLeft(ItemStack stack, World worldIn, EntityLivingBase entityLiving, boolean righthand) {
    	GrappleCustomization custom = this.getCustomization(stack);
    	
  		double angle = custom.angle;
  		double verticalangle = custom.verticalthrowangle;
  		
  		if (entityLiving.isSneaking()) {
  			angle = custom.sneakingangle;
  			verticalangle = custom.sneakingverticalthrowangle;
  		}
  		
  		EntityLivingBase player = entityLiving;
  		
  		vec anglevec = vec.fromAngles(Math.toRadians(-angle), Math.toRadians(verticalangle)); //new vec(0,0,1).rotate_yaw(Math.toRadians(angle)).rotate_pitch(Math.toRadians(verticalangle));
  		anglevec = anglevec.rotate_pitch(Math.toRadians(-player.rotationPitch));
  		anglevec = anglevec.rotate_yaw(Math.toRadians(player.rotationYaw));
        float velx = -MathHelper.sin((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
        float vely = -MathHelper.sin((float) anglevec.getPitch() * 0.017453292F);
        float velz = MathHelper.cos((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
		grappleArrow entityarrow = this.createarrow(stack, worldIn, entityLiving, false, true);// new grappleArrow(worldIn, player, false);
        float extravelocity = (float) vec.motionvec(entityLiving).dist_along(new vec(velx, vely, velz));
        if (extravelocity < 0) { extravelocity = 0; }
        entityarrow.shoot((double) velx, (double) vely, (double) velz, entityarrow.getVelocity() + extravelocity, 0.0F);
        
		worldIn.spawnEntity(entityarrow);
		setArrowLeft(entityLiving, entityarrow);    			
		
		return true;
	}
	
	public void throwRight(ItemStack stack, World worldIn, EntityLivingBase entityLiving, boolean righthand) {
    	GrappleCustomization custom = this.getCustomization(stack);
    	
  		double angle = custom.angle;
  		double verticalangle = custom.verticalthrowangle;
  		if (entityLiving.isSneaking()) {
  			angle = custom.sneakingangle;
  			verticalangle = custom.sneakingverticalthrowangle;
  		}
  		
    	if (!custom.doublehook || angle == 0) {
			grappleArrow entityarrow = this.createarrow(stack, worldIn, entityLiving, righthand, false);
      		vec anglevec = new vec(0,0,1).rotate_pitch(Math.toRadians(verticalangle));
      		anglevec = anglevec.rotate_pitch(Math.toRadians(-entityLiving.rotationPitch));
      		anglevec = anglevec.rotate_yaw(Math.toRadians(entityLiving.rotationYaw));
	        float velx = -MathHelper.sin((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
	        float vely = -MathHelper.sin((float) anglevec.getPitch() * 0.017453292F);
	        float velz = MathHelper.cos((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
	        float extravelocity = (float) vec.motionvec(entityLiving).dist_along(new vec(velx, vely, velz));
	        if (extravelocity < 0) { extravelocity = 0; }
	        entityarrow.shoot((double) velx, (double) vely, (double) velz, entityarrow.getVelocity() + extravelocity, 0.0F);
			setArrowRight(entityLiving, entityarrow);
			worldIn.spawnEntity(entityarrow);
    	} else {
      		EntityLivingBase player = entityLiving;
      		
      		vec anglevec = vec.fromAngles(Math.toRadians(angle), Math.toRadians(verticalangle)); //new vec(0,0,1).rotate_yaw(Math.toRadians(angle)).rotate_pitch(Math.toRadians(verticalangle));
      		anglevec = anglevec.rotate_pitch(Math.toRadians(-player.rotationPitch));
      		anglevec = anglevec.rotate_yaw(Math.toRadians(player.rotationYaw));
	        float velx = -MathHelper.sin((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
	        float vely = -MathHelper.sin((float) anglevec.getPitch() * 0.017453292F);
	        float velz = MathHelper.cos((float) anglevec.getYaw() * 0.017453292F) * MathHelper.cos((float) anglevec.getPitch() * 0.017453292F);
			grappleArrow entityarrow = this.createarrow(stack, worldIn, entityLiving, true, true);//new grappleArrow(worldIn, player, true);
//            entityarrow.shoot(player, (float) anglevec.getPitch(), (float)anglevec.getYaw(), 0.0F, entityarrow.getVelocity(), 0.0F);
	        float extravelocity = (float) vec.motionvec(entityLiving).dist_along(new vec(velx, vely, velz));
	        if (extravelocity < 0) { extravelocity = 0; }
	        entityarrow.shoot((double) velx, (double) vely, (double) velz, entityarrow.getVelocity() + extravelocity, 0.0F);
            
			worldIn.spawnEntity(entityarrow);
			setArrowRight(entityLiving, entityarrow);
		}
	}
	
	public void detachBoth(EntityLivingBase entityLiving) {
		grappleArrow arrow1 = getArrowLeft(entityLiving);
		grappleArrow arrow2 = getArrowRight(entityLiving);

		setArrowLeft(entityLiving, null);
		setArrowRight(entityLiving, null);
		
		if (arrow1 != null) {
			arrow1.removeServer();
		}
		if (arrow2 != null) {
			arrow2.removeServer();
		}

		int id = entityLiving.getEntityId();
		grapplemod.sendtocorrectclient(new GrappleDetachMessage(id), id, entityLiving.world);

		if (grapplemod.attached.contains(id)) {
			grapplemod.attached.remove(new Integer(id));
		}
	}
	
	public void detachLeft(EntityLivingBase entityLiving) {
		grappleArrow arrow1 = getArrowLeft(entityLiving);
		
		setArrowLeft(entityLiving, null);
		
		if (arrow1 != null) {
			arrow1.removeServer();
		}
		
		int id = entityLiving.getEntityId();
		
		// remove controller if hook is attached
		if (getArrowRight(entityLiving) == null) {
			grapplemod.sendtocorrectclient(new GrappleDetachMessage(id), id, entityLiving.world);
		} else {
			grapplemod.sendtocorrectclient(new DetachSingleHookMessage(id, arrow1.getEntityId()), id, entityLiving.world);
		}
		
		if (grapplemod.attached.contains(id)) {
			grapplemod.attached.remove(new Integer(id));
		}
	}
	
	public void detachRight(EntityLivingBase entityLiving) {
		grappleArrow arrow2 = getArrowRight(entityLiving);
		
		setArrowRight(entityLiving, null);
		
		if (arrow2 != null) {
			arrow2.removeServer();
		}
		
		int id = entityLiving.getEntityId();
		
		// remove controller if hook is attached
		if (getArrowLeft(entityLiving) == null) {
			grapplemod.sendtocorrectclient(new GrappleDetachMessage(id), id, entityLiving.world);
		} else {
			grapplemod.sendtocorrectclient(new DetachSingleHookMessage(id, arrow2.getEntityId()), id, entityLiving.world);
		}
		
		if (grapplemod.attached.contains(id)) {
			grapplemod.attached.remove(new Integer(id));
		}
	}
	
    public double getAngle(EntityLivingBase entity, ItemStack stack) {
    	GrappleCustomization custom = this.getCustomization(stack);
    	if (entity.isSneaking()) {
    		return custom.sneakingangle;
    	} else {
    		return custom.angle;
    	}
    }
	
	public grappleArrow createarrow(ItemStack stack, World worldIn, EntityLivingBase entityLiving, boolean righthand, boolean isdouble) {
		grappleArrow arrow = new grappleArrow(worldIn, entityLiving, righthand, this.getCustomization(stack), isdouble);
		grapplemod.addarrow(entityLiving.getEntityId(), arrow);
		return arrow;
	}
    
    @Override
    public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer entityLiving, EnumHand hand)
    {
    	ItemStack stack = entityLiving.getHeldItem(hand);
        if (!worldIn.isRemote) {
	        this.dorightclick(stack, worldIn, entityLiving, hand == EnumHand.MAIN_HAND);
        }
        entityLiving.setActiveHand(hand);
        return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
    }
	

	@Override
	public void onPlayerStoppedUsing(ItemStack stack, World worldIn,
			EntityLivingBase entityLiving, int timeLeft) {
		if (!worldIn.isRemote) {
//			stack.getSubCompound("grapplemod", true).setBoolean("extended", (this.getArrow(entityLiving, worldIn) != null));
		}
		super.onPlayerStoppedUsing(stack, worldIn, entityLiving, timeLeft);
	}

	/**
	 * returns the action that specifies what animation to play when the items is being used
	 */
    @Override
	public EnumAction getItemUseAction(ItemStack par1ItemStack)
	{
		return EnumAction.NONE;
	}

    @Override
    public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity entity)
    {
    	return true;
    }
    
	@Override
	public void onCustomKeyDown(ItemStack stack, EntityPlayer player, KeypressItem.Keys key, boolean ismainhand) {
		if (player.world.isRemote) {
			if (key == KeypressItem.Keys.LAUNCHER) {
				if (this.getCustomization(stack).enderstaff) {
					grapplemod.proxy.launchplayer(player);
				}
			} else if (key == KeypressItem.Keys.THROWLEFT || key == KeypressItem.Keys.THROWRIGHT || key == KeypressItem.Keys.THROWBOTH) {
				grapplemod.network.sendToServer(new KeypressMessage(key, true));
			} else if (key == KeypressItem.Keys.ROCKET) {
				GrappleCustomization custom = this.getCustomization(stack);
				if (custom.rocket) {
					grapplemod.proxy.startrocket(player, custom);
				}
			}
		} else {
			if (key == KeypressItem.Keys.THROWBOTH) {
	        	throwBoth(stack, player.world, player, ismainhand);
			} else if (key == KeypressItem.Keys.THROWLEFT) {
				grappleArrow arrow1 = getArrowLeft(player);

	    		if (arrow1 != null) {
	    			detachLeft(player);
		    		return;
				}
				
				boolean threw = throwLeft(stack, player.world, player, ismainhand);

				if (threw) {
					stack.damageItem(1, player);
			        player.world.playSound((EntityPlayer)null, player.posX, player.posY, player.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + 2.0F * 0.5F);
				}
			} else if (key == KeypressItem.Keys.THROWRIGHT) {
				grappleArrow arrow2 = getArrowRight(player);

	    		if (arrow2 != null) {
	    			detachRight(player);
		    		return;
				}
				
				throwRight(stack, player.world, player, ismainhand);

				stack.damageItem(1, player);
		        player.world.playSound((EntityPlayer)null, player.posX, player.posY, player.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + 2.0F * 0.5F);
			}
		}
	}
	
	@Override
	public void onCustomKeyUp(ItemStack stack, EntityPlayer player, KeypressItem.Keys key, boolean ismainhand) {
		if (player.world.isRemote) {
			if (key == KeypressItem.Keys.THROWLEFT || key == KeypressItem.Keys.THROWRIGHT || key == KeypressItem.Keys.THROWBOTH) {
				grapplemod.network.sendToServer(new KeypressMessage(key, false));
			}
		} else {
	    	GrappleCustomization custom = this.getCustomization(stack);
	    	
	    	if (custom.detachonkeyrelease) {
	    		grappleArrow arrow_left = getArrowLeft(player);
	    		grappleArrow arrow_right = getArrowRight(player);
	    		
				if (key == KeypressItem.Keys.THROWBOTH) {
					detachBoth(player);
				} else if (key == KeypressItem.Keys.THROWLEFT) {
		    		if (arrow_left != null) detachLeft(player);
				} else if (key == KeypressItem.Keys.THROWRIGHT) {
		    		if (arrow_right != null) detachRight(player);
				}
	    	}
		}
	}
   
    @Override
    public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase attacker)
    {
    	return true;
    }
   
    @Override
    public boolean onBlockStartBreak(ItemStack itemstack, BlockPos k, EntityPlayer player)
    {
      return true;
    }
    
    public GrappleCustomization getCustomization(ItemStack itemstack) {
    	if (itemstack.hasTagCompound()) {
        	GrappleCustomization custom = new GrappleCustomization();
    		custom.loadNBT(itemstack.getTagCompound());
        	return custom;
    	} else {
    		GrappleCustomization custom = this.getDefaultCustomization();

			NBTTagCompound nbt = custom.writeNBT();
			
			itemstack.setTagCompound(nbt);
    		
    		return custom;
    	}
    }
    
    public GrappleCustomization getDefaultCustomization() {
    	return new GrappleCustomization();
    }
    
	@Override
    @SideOnly(Side.CLIENT)
	public void addInformation(ItemStack stack, World world, List<String> list, ITooltipFlag par4)
	{
		GrappleCustomization custom = getCustomization(stack);

		
		if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
			if (!custom.detachonkeyrelease) {
				list.add(ClientProxyClass.key_boththrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throw.desc"));
				list.add(ClientProxyClass.key_boththrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.release.desc"));
				list.add(grapplemod.proxy.localize("grappletooltip.double.desc") + ClientProxyClass.key_boththrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.releaseandthrow.desc"));
			} else {
				list.add(ClientProxyClass.key_boththrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwhold.desc"));
			}
			list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindForward) + ", " +
					grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindLeft) + ", " +
					grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindBack) + ", " +
					grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindRight) +
					" " + grapplemod.proxy.localize("grappletooltip.swing.desc"));
			list.add(ClientProxyClass.key_jumpanddetach.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.jump.desc"));
			list.add(ClientProxyClass.key_slow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.slow.desc"));
			list.add(ClientProxyClass.key_climb.getDisplayName() + " + " + grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindForward) + " / " + 
					ClientProxyClass.key_climbup.getDisplayName() + 
					" " + grapplemod.proxy.localize("grappletooltip.climbup.desc"));
			list.add(ClientProxyClass.key_climb.getDisplayName() + " + " + grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindBack) + " / " + 
					ClientProxyClass.key_climbdown.getDisplayName() + 
					" " + grapplemod.proxy.localize("grappletooltip.climbdown.desc"));
			if (custom.enderstaff) {
				list.add(ClientProxyClass.key_enderlaunch.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.enderlaunch.desc"));
			}
			if (custom.rocket) {
				list.add(ClientProxyClass.key_rocket.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.rocket.desc"));
			}
			if (custom.motor) {
				if (custom.motorwhencrouching && !custom.motorwhennotcrouching) {
					list.add(ClientProxyClass.key_motoronoff.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.motoron.desc"));
				}
				else if (!custom.motorwhencrouching && custom.motorwhennotcrouching) {
					list.add(ClientProxyClass.key_motoronoff.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.motoroff.desc"));
				}
			}
			if (custom.doublehook) {
				if (!custom.detachonkeyrelease) {
					list.add(ClientProxyClass.key_leftthrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwleft.desc"));
					list.add(ClientProxyClass.key_rightthrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwright.desc"));
				} else {
					list.add(ClientProxyClass.key_leftthrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwlefthold.desc"));
					list.add(ClientProxyClass.key_rightthrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwrighthold.desc"));
				}
			} else {
				list.add(ClientProxyClass.key_rightthrow.getDisplayName() + " " + grapplemod.proxy.localize("grappletooltip.throwalt.desc"));
			}
		} else {
			if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
				for (String option : GrappleCustomization.booleanoptions) {
					if (custom.isoptionvalid(option) && custom.getBoolean(option)) {
						list.add(grapplemod.proxy.localize(custom.getName(option)));
					}
				}
				for (String option : GrappleCustomization.doubleoptions) {
					if (custom.isoptionvalid(option)) {
						list.add(grapplemod.proxy.localize(custom.getName(option)) + ": " + Math.floor(custom.getDouble(option) * 100) / 100);
					}
				}
			} else {
				if (custom.doublehook) {
					list.add(grapplemod.proxy.localize(custom.getName("doublehook")));
				}
				if (custom.motor) {
					if (custom.smartmotor) {
						list.add(grapplemod.proxy.localize(custom.getName("smartmotor")));
					} else {
						list.add(grapplemod.proxy.localize(custom.getName("motor")));
					}
				}
				if (custom.enderstaff) {
					list.add(grapplemod.proxy.localize(custom.getName("enderstaff")));
				}
				if (custom.rocket) {
					list.add(grapplemod.proxy.localize(custom.getName("rocket")));
				}
				if (custom.attract) {
					list.add(grapplemod.proxy.localize(custom.getName("attract")));
				}
				if (custom.repel) {
					list.add(grapplemod.proxy.localize(custom.getName("repel")));
				}
				
				list.add("");
				list.add(grapplemod.proxy.localize("grappletooltip.shiftcontrols.desc"));
				list.add(grapplemod.proxy.localize("grappletooltip.controlconfiguration.desc"));
			}
		}
		

	}

	
	@Override
    @SideOnly(Side.CLIENT)
    public ItemStack getDefaultInstance()
    {
        ItemStack stack = new ItemStack(this);
        this.getCustomization(stack);
        return stack;
    }
	
	@Override
    public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items)
    {
        if (this.isInCreativeTab(tab))
        {
        	ItemStack stack = new ItemStack(this);
        	this.getCustomization(stack);
            items.add(stack);
        }
    }
	
	@Override
	public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player) {
		int id = player.getEntityId();
		grapplemod.sendtocorrectclient(new GrappleDetachMessage(id), id, player.world);
		
		if (grapplemod.attached.contains(id)) {
			grapplemod.attached.remove(id);
		}
		
		if (grapplearrows1.containsKey(player)) {
			grappleArrow arrow1 = grapplearrows1.get(player);
			setArrowLeft(player, null);
			if (arrow1 != null) {
				arrow1.removeServer();
			}
		}
		
		if (grapplearrows2.containsKey(player)) {
			grappleArrow arrow2 = grapplearrows2.get(player);
			setArrowLeft(player, null);
			if (arrow2 != null) {
				arrow2.removeServer();
			}
		}
		
		return super.onDroppedByPlayer(item, player);
	}
} public class launcherItem extends Item {
	
	public launcherItem() {
		super();
		maxStackSize = 1;
		setFull3D();
		setUnlocalizedName("launcheritem");
		
		this.setMaxDamage(500);
		
		setCreativeTab(grapplemod.tabGrapplemod);
		
		MinecraftForge.EVENT_BUS.register(this);
	}
	
	@Override
	public int getMaxItemUseDuration(ItemStack par1ItemStack)
	{
		return 72000;
	}

	public void dorightclick(ItemStack stack, World worldIn, EntityPlayer player) {
		if (worldIn.isRemote) {
			grapplemod.proxy.launchplayer(player);
		}
	}
	
    @Override
    public EnumActionResult onItemUse(EntityPlayer playerIn, World worldIn, BlockPos blockpos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
    {
    	ItemStack stack = playerIn.getHeldItem(hand);
        this.dorightclick(stack, worldIn, playerIn);
        
    	return EnumActionResult.SUCCESS;
	}
    
    @Override
	public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer entityLiving, EnumHand hand)
	{
    	ItemStack stack = entityLiving.getHeldItem(hand);
    	this.dorightclick(stack, worldIn, entityLiving);
        
        return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
    }

    @Override
    public EnumAction getItemUseAction(ItemStack par1ItemStack)
	{
		return EnumAction.NONE;
	}
    
	@Override
    @SideOnly(Side.CLIENT)
	public void addInformation(ItemStack stack, World world, List<String> list, ITooltipFlag par4)
	{
		list.add("Launches player");
		list.add("");
		list.add("Use crosshairs to aim");
		list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindUseItem) + " - Launch player");
	}
} public class repeller extends Item {
	public repeller() {
		super();
		maxStackSize = 1;
		setFull3D();
		setUnlocalizedName("repeller");
		
		this.setMaxDamage(500);
		
		setCreativeTab(grapplemod.tabGrapplemod);
		
		MinecraftForge.EVENT_BUS.register(this);
	}
	
	@Override
	public int getMaxItemUseDuration(ItemStack par1ItemStack)
	{
		return 72000;
	}

	public void dorightclick(ItemStack stack, World worldIn, EntityPlayer player) {
		if (worldIn.isRemote) {
			int playerid = player.getEntityId();
			if (grapplemod.controllers.containsKey(playerid) && grapplemod.controllers.get(playerid).controllerid != grapplemod.AIRID) {
				grappleController controller = grapplemod.controllers.get(playerid);
				controller.unattach();
			} else {
				grapplemod.proxy.createControl(grapplemod.REPELID, -1, playerid, worldIn, new vec(0,0,0), null, null);
			}
		}
	}

    @Override
	public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand hand)
    {
    	ItemStack itemStackIn = playerIn.getHeldItem(hand);
        this.dorightclick(itemStackIn, worldIn, playerIn);
        
        return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, itemStackIn);
    }

    @Override
    public EnumAction getItemUseAction(ItemStack par1ItemStack)
	{
		return EnumAction.NONE;
	}
    
	@Override
    @SideOnly(Side.CLIENT)
	public void addInformation(ItemStack stack, World world, List<String> list, ITooltipFlag par4)
	{
		list.add("Player is repelled by nearby blocks");
		list.add("Can be used with ender staff");
		list.add("");
		list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindUseItem) + " - Turn on");
		list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindUseItem) + " again - Turn off");
		list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindSneak) + " - Slow down");
		list.add(grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindForward) + ", " +
				grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindLeft) + ", " +
				grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindBack) + ", " +
				grapplemod.proxy.getkeyname(CommonProxyClass.keys.keyBindRight) +
				" - Move");
	}
} public class DoubleMotorHook extends grappleBow {
	public DoubleMotorHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.doublehook = true;
    	custom.motor = true;
    	custom.motormaxspeed = 10;
    	custom.reelin = false;
    	custom.sticky = true;
    	
    	custom.hookgravity = 50;
    	custom.verticalthrowangle = 30;
    	custom.sneakingverticalthrowangle = 25;
    	custom.reelin = false;
    	
    	custom.motorwhencrouching = true;
    	
    	custom.smartdoublemotor = true;
//     	custom.smartmotor = true;
    	
    	custom.angle = 25;
    	custom.sneakingangle = 0;
    	
    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = 20;
    	
    	custom.playermovementmult = 2;
    	
    	return custom;
    }
} public class EnderHook extends grappleBow {
	public EnderHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.enderstaff = true;

    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = GrappleConfig.options.upgraded_throwspeed;
    	
    	return custom;
    }
} public class MagnetHook extends grappleBow {
	public MagnetHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.attract = true;
    	custom.repel = true;

    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = GrappleConfig.options.upgraded_throwspeed;
    	
    	return custom;
    }
} public class MotorHook extends grappleBow {
	public MotorHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.motor = true;

    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = GrappleConfig.options.upgraded_throwspeed;

    	custom.playermovementmult = 2;
    	
    	return custom;
    }
} public class RocketDoubleMotorHook extends grappleBow {
	public RocketDoubleMotorHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.doublehook = true;
    	custom.motor = true;
    	custom.motormaxspeed = 10;
    	custom.reelin = false;
    	custom.sticky = true;
    	
    	custom.hookgravity = 50;
    	custom.verticalthrowangle = 30;
    	custom.sneakingverticalthrowangle = 25;
    	custom.reelin = false;
    	
    	custom.motorwhencrouching = true;
    	
    	custom.smartdoublemotor = true;
//     	custom.smartmotor = true;
    	
    	custom.angle = 25;
    	custom.sneakingangle = 0;
    	
    	custom.rocket = true;
    	custom.rocket_vertical_angle = 30;
    	
    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = 20;
    	
    	custom.playermovementmult = 2;
    	
    	return custom;
    }
} public class RocketHook extends grappleBow {
	public RocketHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.rocket = true;
    	// custom.rocket_vertical_angle = 30;

    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = GrappleConfig.options.upgraded_throwspeed;
    	
    	return custom;
    }
} public class SmartHook extends grappleBow {
	public SmartHook() {
		super();
	}
	
	@Override
    public GrappleCustomization getDefaultCustomization() {
    	GrappleCustomization custom = new GrappleCustomization();
    	custom.motor = true;
    	custom.smartmotor = true;

    	custom.maxlen = GrappleConfig.options.upgraded_maxlen;
    	custom.throwspeed = GrappleConfig.options.upgraded_throwspeed;

    	custom.playermovementmult = 2;
    	
    	return custom;
    }
} public class BaseUpgradeItem extends Item {
	public String unlocalizedname;
	public grapplemod.upgradeCategories category;
	
	public BaseUpgradeItem() {
		super();
		maxStackSize = 1;
		setvars();
		setFull3D();
		setUnlocalizedName(unlocalizedname);
		
		setCreativeTab(grapplemod.tabGrapplemod);
	}
	
	public void setvars() {
		unlocalizedname = "baseupgradeitem";
		category = null;
		maxStackSize = 64;
	}
} public class DoubleUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "doubleupgradeitem";
		category = grapplemod.upgradeCategories.DOUBLE;
	}
} public class ForcefieldUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "forcefieldupgradeitem";
		category = grapplemod.upgradeCategories.FORCEFIELD;
	}
} public class LimitsUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "limitsupgradeitem";
		category = grapplemod.upgradeCategories.LIMITS;
	}
} public class MagnetUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "magnetupgradeitem";
		category = grapplemod.upgradeCategories.MAGNET;
	}
} public class MotorUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "motorupgradeitem";
		category = grapplemod.upgradeCategories.MOTOR;
	}
} public class RocketUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "rocketupgradeitem";
		category = grapplemod.upgradeCategories.ROCKET;
	}
} public class RopeUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "ropeupgradeitem";
		category = grapplemod.upgradeCategories.ROPE;
	}
} public class StaffUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "staffupgradeitem";
		category = grapplemod.upgradeCategories.STAFF;
	}
} public class SwingUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "swingupgradeitem";
		category = grapplemod.upgradeCategories.SWING;
	}
} public class ThrowUpgradeItem extends BaseUpgradeItem {
	public void setvars() {
		unlocalizedname = "throwupgradeitem";
		category = grapplemod.upgradeCategories.THROW;
	}
} public class DetachSingleHookMessage implements IMessage {
   
	public int id;
	public int hookid;

    public DetachSingleHookMessage() { }

    public DetachSingleHookMessage(int id, int hookid) {
    	this.id = id;
    	this.hookid = hookid;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.id = buf.readInt();
    	this.hookid = buf.readInt();
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.id);
    	buf.writeInt(this.hookid);
    }

    public static class Handler implements IMessageHandler<DetachSingleHookMessage, IMessage> {
    	public class runner implements Runnable {
    		DetachSingleHookMessage message;
    		MessageContext ctx;
    		public runner(DetachSingleHookMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	grapplemod.receiveGrappleDetachHook(message.id, message.hookid);
            }
    	}
    	
       
        @Override
        public IMessage onMessage(DetachSingleHookMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft(); // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class GrappleAttachMessage implements IMessage {
   
	public int id;
	public double x;
	public double y;
	public double z;
	public int controlid;
	public int entityid;
	public BlockPos blockpos;
	public LinkedList<vec> segments;
	public LinkedList<EnumFacing> segmenttopsides;
	public LinkedList<EnumFacing> segmentbottomsides;
	public GrappleCustomization custom;

    public GrappleAttachMessage() { }

    public GrappleAttachMessage(int id, double x, double y, double z, int controlid, int entityid, BlockPos blockpos, LinkedList<vec> segments, LinkedList<EnumFacing> segmenttopsides, LinkedList<EnumFacing> segmentbottomsides, GrappleCustomization custom) {
    	this.id = id;
        this.x = x;
        this.y = y;
        this.z = z;
        this.controlid = controlid;
        this.entityid = entityid;
        this.blockpos = blockpos;
        this.segments = segments;
        this.segmenttopsides = segmenttopsides;
        this.segmentbottomsides = segmentbottomsides;
        this.custom = custom;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.id = buf.readInt();
        this.x = buf.readDouble();
        this.y = buf.readDouble();
        this.z = buf.readDouble();
        this.controlid = buf.readInt();
        this.entityid = buf.readInt();
        int blockx = buf.readInt();
        int blocky = buf.readInt();
        int blockz = buf.readInt();
        this.blockpos = new BlockPos(blockx, blocky, blockz);
        
        this.custom = new GrappleCustomization();
        this.custom.readFromBuf(buf);
        
        int size = buf.readInt();
        this.segments = new LinkedList<vec>();
        this.segmentbottomsides = new LinkedList<EnumFacing>();
        this.segmenttopsides = new LinkedList<EnumFacing>();

		segments.add(new vec(0, 0, 0));
		segmentbottomsides.add(null);
		segmenttopsides.add(null);
		
		for (int i = 1; i < size-1; i++) {
        	this.segments.add(new vec(buf.readDouble(), buf.readDouble(), buf.readDouble()));
        	this.segmentbottomsides.add(EnumFacing.getFront(buf.readInt()));
        	this.segmenttopsides.add(EnumFacing.getFront(buf.readInt()));
        }
		
		segments.add(new vec(0, 0, 0));
		segmentbottomsides.add(null);
		segmenttopsides.add(null);
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.id);
        buf.writeDouble(this.x);
        buf.writeDouble(this.y);
        buf.writeDouble(this.z);
        buf.writeInt(this.controlid);
        buf.writeInt(this.entityid);
        buf.writeInt(this.blockpos.getX());
        buf.writeInt(this.blockpos.getY());
        buf.writeInt(this.blockpos.getZ());
        
        this.custom.writeToBuf(buf);
        
        buf.writeInt(this.segments.size());
        for (int i = 1; i < this.segments.size()-1; i++) {
        	buf.writeDouble(this.segments.get(i).x);
        	buf.writeDouble(this.segments.get(i).y);
        	buf.writeDouble(this.segments.get(i).z);
        	buf.writeInt(this.segmentbottomsides.get(i).getIndex());
        	buf.writeInt(this.segmenttopsides.get(i).getIndex());
        }
    }

    public static class Handler implements IMessageHandler<GrappleAttachMessage, IMessage> {
    	public class runner implements Runnable {
    		GrappleAttachMessage message;
    		MessageContext ctx;
    		public runner(GrappleAttachMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	World world = Minecraft.getMinecraft().world;
            	Entity grapple = world.getEntityByID(message.id);
            	if (grapple instanceof grappleArrow) {
	            	((grappleArrow) grapple).clientAttach(message.x, message.y, message.z);
	            	SegmentHandler segmenthandler = ((grappleArrow) grapple).segmenthandler;
	            	segmenthandler.segments = message.segments;
	            	segmenthandler.segmentbottomsides = message.segmentbottomsides;
	            	segmenthandler.segmenttopsides = message.segmenttopsides;
	            	
	            	Entity player = world.getEntityByID(message.entityid);
	            	segmenthandler.forceSetPos(new vec(message.x, message.y, message.z), vec.positionvec(player));
            	} else {
            	}
            	            	
            	grapplemod.proxy.createControl(message.controlid, message.id, message.entityid, world, new vec(message.x, message.y, message.z), message.blockpos, message.custom);
            }
    	}
    	
       
        @Override
        public IMessage onMessage(GrappleAttachMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft();
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; 
        }
    }
} public class GrappleAttachPosMessage implements IMessage {
   
	public int id;
	public double x;
	public double y;
	public double z;

    public GrappleAttachPosMessage() { }

    public GrappleAttachPosMessage(int id, double x, double y, double z) {
    	this.id = id;
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.id = buf.readInt();
        this.x = buf.readDouble();
        this.y = buf.readDouble();
        this.z = buf.readDouble();
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.id);
        buf.writeDouble(this.x);
        buf.writeDouble(this.y);
        buf.writeDouble(this.z);
    }

    public static class Handler implements IMessageHandler<GrappleAttachPosMessage, IMessage> {
    	public class runner implements Runnable {
    		GrappleAttachPosMessage message;
    		MessageContext ctx;
    		public runner(GrappleAttachPosMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	World world = Minecraft.getMinecraft().world;
            	Entity grapple = world.getEntityByID(message.id);
            	if (grapple instanceof grappleArrow) {
	            	((grappleArrow) grapple).setAttachPos(message.x, message.y, message.z);
            	}
            }
    	}
    	
       
        @Override
        public IMessage onMessage(GrappleAttachPosMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft(); // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class GrappleDetachMessage implements IMessage {
   
	public int id;

    public GrappleDetachMessage() { }

    public GrappleDetachMessage(int id) {
    	this.id = id;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.id = buf.readInt();
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.id);
    }

    public static class Handler implements IMessageHandler<GrappleDetachMessage, IMessage> {
    	public class runner implements Runnable {
    		GrappleDetachMessage message;
    		MessageContext ctx;
    		public runner(GrappleDetachMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	grapplemod.receiveGrappleDetach(message.id);
            }
    	}
    	
       
        @Override
        public IMessage onMessage(GrappleDetachMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft(); // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class GrappleEndMessage implements IMessage {
   
	public int entityid;
	public HashSet<Integer> arrowIds;

    public GrappleEndMessage() { }

    public GrappleEndMessage(int entityid, HashSet<Integer> arrowIds) {
    	this.entityid = entityid;
    	this.arrowIds = arrowIds;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.entityid = buf.readInt();
    	int size = buf.readInt();
    	this.arrowIds = new HashSet<Integer>();
    	for (int i = 0; i < size; i++) {
    		this.arrowIds.add(buf.readInt());
    	}
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.entityid);
    	buf.writeInt(this.arrowIds.size());
    	for (int id : this.arrowIds) {
        	buf.writeInt(id);
    	}
    }

    public static class Handler implements IMessageHandler<GrappleEndMessage, IMessage> {
    	public class runner implements Runnable {
    		GrappleEndMessage message;
    		MessageContext ctx;
    		public runner(GrappleEndMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {

				int id = message.entityid;
				
				World w = ctx.getServerHandler().player.world;
				
				grapplemod.receiveGrappleEnd(id, w, message.arrowIds);
            }
    	}
    	
        @Override
        public IMessage onMessage(GrappleEndMessage message, MessageContext ctx) {

        	IThreadListener mainThread = (WorldServer) ctx.getServerHandler().player.world; // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class GrappleModifierMessage implements IMessage {
   
	public BlockPos pos;
	public GrappleCustomization custom;

    public GrappleModifierMessage() { }

    public GrappleModifierMessage(BlockPos pos, GrappleCustomization custom) {
    	this.pos = pos;
    	this.custom = custom;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.pos = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt());
    	this.custom = new GrappleCustomization();
    	this.custom.readFromBuf(buf);
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.pos.getX());
    	buf.writeInt(this.pos.getY());
    	buf.writeInt(this.pos.getZ());
    	this.custom.writeToBuf(buf);
    }

    public static class Handler implements IMessageHandler<GrappleModifierMessage, IMessage> {
    	public class runner implements Runnable {
    		GrappleModifierMessage message;
    		MessageContext ctx;
    		public runner(GrappleModifierMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
				World w = ctx.getServerHandler().player.world;
				
				TileEntity ent = w.getTileEntity(message.pos);
				
				if (ent instanceof TileEntityGrappleModifier) {
					((TileEntityGrappleModifier) ent).setCustomizationServer(message.custom);
				}
            }
    	}
    	
       
        @Override
        public IMessage onMessage(GrappleModifierMessage message, MessageContext ctx) {

        	IThreadListener mainThread = (WorldServer) ctx.getServerHandler().player.world; // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class KeypressMessage implements IMessage {
	
	KeypressItem.Keys key;
	boolean isDown;

    public KeypressMessage() { }

    public KeypressMessage(KeypressItem.Keys thekey, boolean isDown) {
    	this.key = thekey;
    	this.isDown = isDown;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.key = KeypressItem.Keys.values()[buf.readInt()];
    	this.isDown = buf.readBoolean();
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.key.ordinal());
    	buf.writeBoolean(this.isDown);
    }

    public static class Handler implements IMessageHandler<KeypressMessage, IMessage> {
    	public class runner implements Runnable {
    		KeypressMessage message;
    		MessageContext ctx;
    		public runner(KeypressMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
				World w = ctx.getServerHandler().player.world;
				
				grapplemod.receiveKeypress(ctx.getServerHandler().player, this.message.key, this.message.isDown);
            }
    	}
    	
        @Override
        public IMessage onMessage(KeypressMessage message, MessageContext ctx) {

        	IThreadListener mainThread = (WorldServer) ctx.getServerHandler().player.world; // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; // no response in this case
        }
    }
} public class LoggedInMessage implements IMessage {
    GrappleConfig.Config conf = null;

    public LoggedInMessage() { }
    
    public LoggedInMessage(GrappleConfig.Config serverconf) {
    	this.conf = serverconf;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	Class<GrappleConfig.Config> confclass = GrappleConfig.Config.class;
    	Field[] fields = confclass.getDeclaredFields();
    	Arrays.sort(fields, new Comparator<Field>() {
    	    @Override
    	    public int compare(Field o1, Field o2) {
    	        return o1.getName().compareTo(o2.getName());
    	    }
    	});
    	
    	this.conf = new GrappleConfig.Config();
    	
    	for (Field field : fields) {
    		Type fieldtype = field.getGenericType();
    		try {
        		if (fieldtype.getTypeName().equals("int")) {
        			field.setInt(this.conf, buf.readInt());
        		} else if (fieldtype.getTypeName().equals("double")) {
        			field.setDouble(this.conf, buf.readDouble());
        		} else if (fieldtype.getTypeName().equals("boolean")) {
        			field.setBoolean(this.conf, buf.readBoolean());
        		} else if (fieldtype.getTypeName().equals("java.lang.String")) {
        			int len = buf.readInt();
        			CharSequence charseq = buf.readCharSequence(len, Charset.defaultCharset());
        			field.set(this.conf, charseq.toString());
        		} else {
        			System.out.println("Unknown Type");
        			System.out.println(fieldtype.getTypeName());
        		}
    		} catch (IllegalAccessException e) {
    			System.out.println(e);
    		}
    	}
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	Class<GrappleConfig.Config> confclass = GrappleConfig.Config.class;
    	Field[] fields = confclass.getDeclaredFields();
    	Arrays.sort(fields, new Comparator<Field>() {
    	    @Override
    	    public int compare(Field o1, Field o2) {
    	        return o1.getName().compareTo(o2.getName());
    	    }
    	});
    	
    	for (Field field : fields) {
    		Type fieldtype = field.getGenericType();
    		try {
        		if (fieldtype.getTypeName().equals("int")) {
        			buf.writeInt(field.getInt(this.conf));
        		} else if (fieldtype.getTypeName().equals("double")) {
        			buf.writeDouble(field.getDouble(this.conf));
        		} else if (fieldtype.getTypeName().equals("boolean")) {
        			buf.writeBoolean(field.getBoolean(this.conf));
        		} else if (fieldtype.getTypeName().equals("java.lang.String")) {
        			String str = (String) field.get(this.conf);
        			buf.writeInt(str.length());
        			buf.writeCharSequence(str.subSequence(0, str.length()), Charset.defaultCharset());
        		} else {
        			System.out.println("Unknown Type");
        			System.out.println(fieldtype.getTypeName());
        		}
    		} catch (IllegalAccessException e) {
    			System.out.println(e);
    		}
    	}
    }

    public static class Handler implements IMessageHandler<LoggedInMessage, IMessage> {
    	public class runner implements Runnable {
    		LoggedInMessage message;
    		MessageContext ctx;
    		public runner(LoggedInMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	GrappleConfig.setserveroptions(this.message.conf);
            }
    	}
    	
       
        @Override
        public IMessage onMessage(LoggedInMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft();
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; 
        }
    }
} public class PlayerMovementMessage implements IMessage {
   
	public int entityId;
	public double x;
	public double y;
	public double z;
	public double mx;
	public double my;
	public double mz;
	
	public PlayerMovementMessage() {
	}
	
    public PlayerMovementMessage(int entityId, double x, double y, double z, double mx, double my, double mz) {
    	this.entityId = entityId;
    	this.x = x;
    	this.y = y;
    	this.z = z;
    	this.mx = mx;
    	this.my = my;
    	this.mz = mz;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	try {
	    	this.entityId = buf.readInt();
	    	this.x = buf.readDouble();
	    	this.y = buf.readDouble();
	    	this.z = buf.readDouble();
	    	this.mx = buf.readDouble();
	    	this.my = buf.readDouble();
	    	this.mz = buf.readDouble();
    	} catch (Exception e) {
    		System.out.print("Playermovement error: ");
    		System.out.println(buf);
    	}
    }

    @Override
    public void toBytes(ByteBuf buf) {
        buf.writeInt(entityId);
        buf.writeDouble(x);
        buf.writeDouble(y);
        buf.writeDouble(z);
        buf.writeDouble(mx);
        buf.writeDouble(my);
        buf.writeDouble(mz);
        
    }

    public static class Handler implements IMessageHandler<PlayerMovementMessage, IMessage> {
       
    	public class runner implements Runnable {
    		PlayerMovementMessage message;
    		MessageContext ctx;
    		
    		public runner(PlayerMovementMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
                World world = ctx.getServerHandler().player.world;
                Entity entity = world.getEntityByID(message.entityId);
                if (entity == null) {return;}
                if(entity instanceof EntityPlayerMP) {
					EntityPlayerMP referencedPlayer = (EntityPlayerMP)entity;
					if(ctx.getServerHandler().player.getGameProfile().equals(referencedPlayer.getGameProfile())) {
						entity.posX = message.x;
						entity.posY = message.y;
						entity.posZ = message.z;
						entity.motionX = message.mx;
						entity.motionY = message.my;
						entity.motionZ = message.mz;
						EntityPlayerMP player = ((EntityPlayerMP) entity);
						//                	player.connection.update();
						Method capturePositionMethod = grapplemod.proxy.getCapturePositionMethod();
						if (capturePositionMethod != null) {
							try {
								capturePositionMethod.invoke(player.connection);
							} catch (IllegalAccessException e) {
								e.printStackTrace();
							} catch (IllegalArgumentException e) {
								e.printStackTrace();
							} catch (InvocationTargetException e) {
								e.printStackTrace();
							}
						} else {
							System.out.println("Error capturePositionMethod is null");
						}
						
						if (!player.onGround) {
							if (message.my >= 0) {
								player.fallDistance = 0;
							} else {
								double gravity = 0.05 * 2;
								// d = v^2 / 2g
						    	player.fallDistance = (float) (Math.pow(message.my, 2) / (2 * gravity));
							}
						}
					}
                }
            }
    	}
    	
        @Override
        public IMessage onMessage(PlayerMovementMessage message, MessageContext ctx) {
        	IThreadListener mainThread = (WorldServer) ctx.getServerHandler().player.world; // or Minecraft.getMinecraft() on the client
            mainThread.addScheduledTask(new runner(message, ctx));
            
            return null; // no response in this case
        }
    }
} public class SegmentMessage implements IMessage {
   
	public int id;
	public boolean add;
	public int index;
	public vec pos;
	public EnumFacing topfacing;
	public EnumFacing bottomfacing;


    public SegmentMessage() { }

    public SegmentMessage(int id, boolean add, int index, vec pos, EnumFacing topfacing, EnumFacing bottomfacing) {
    	this.id = id;
    	this.add = add;
    	this.index = index;
    	this.pos = pos;
    	this.topfacing = topfacing;
    	this.bottomfacing = bottomfacing;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
    	this.id = buf.readInt();
    	this.add = buf.readBoolean();
    	this.index = buf.readInt();
    	this.pos = new vec(buf.readDouble(), buf.readDouble(), buf.readDouble());
    	this.topfacing = EnumFacing.getFront(buf.readInt());
    	this.bottomfacing = EnumFacing.getFront(buf.readInt());
    }

    @Override
    public void toBytes(ByteBuf buf) {
    	buf.writeInt(this.id);
    	buf.writeBoolean(this.add);
    	buf.writeInt(this.index);
    	buf.writeDouble(pos.x);
    	buf.writeDouble(pos.y);
    	buf.writeDouble(pos.z);
    	buf.writeInt(this.topfacing.getIndex());
    	buf.writeInt(this.bottomfacing.getIndex());
    }

    public static class Handler implements IMessageHandler<SegmentMessage, IMessage> {
    	public class runner implements Runnable {
    		SegmentMessage message;
    		MessageContext ctx;
    		public runner(SegmentMessage message, MessageContext ctx) {
    			super();
    			this.message = message;
    			this.ctx = ctx;
    		}
    		
            @Override
            public void run() {
            	World world = Minecraft.getMinecraft().world;
            	Entity grapple = world.getEntityByID(message.id);
            	if (grapple == null) {
            		return;
            	}
            	
            	if (grapple instanceof grappleArrow) {
            		SegmentHandler segmenthandler = ((grappleArrow) grapple).segmenthandler;
            		if (message.add) {
            			segmenthandler.actuallyaddsegment(message.index, message.pos, message.bottomfacing, message.topfacing);
            		} else {
            			segmenthandler.removesegment(message.index);
            		}
            	} else {
            	}
            }
    	}
    	
       
        @Override
        public IMessage onMessage(SegmentMessage message, MessageContext ctx) {

        	IThreadListener mainThread = Minecraft.getMinecraft();
            mainThread.addScheduledTask(new runner(message, ctx));

            return null; 
        }
    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import com.yyon.grapplinghook.blocks.BlockGrappleModifier;
import com.yyon.grapplinghook.blocks.TileEntityGrappleModifier;
import com.yyon.grapplinghook.controllers.airfrictionController;
import com.yyon.grapplinghook.controllers.grappleController;
import com.yyon.grapplinghook.controllers.repelController;
import com.yyon.grapplinghook.enchantments.DoublejumpEnchantment;
import com.yyon.grapplinghook.enchantments.SlidingEnchantment;
import com.yyon.grapplinghook.enchantments.WallrunEnchantment;
import com.yyon.grapplinghook.entities.grappleArrow;
import com.yyon.grapplinghook.items.KeypressItem;
import com.yyon.grapplinghook.items.KeypressItem.Keys;
import com.yyon.grapplinghook.items.LongFallBoots;
import com.yyon.grapplinghook.items.grappleBow;
import com.yyon.grapplinghook.items.launcherItem;
import com.yyon.grapplinghook.items.repeller;
import com.yyon.grapplinghook.items.alternategrapple.DoubleMotorHook;
import com.yyon.grapplinghook.items.alternategrapple.EnderHook;
import com.yyon.grapplinghook.items.alternategrapple.MagnetHook;
import com.yyon.grapplinghook.items.alternategrapple.MotorHook;
import com.yyon.grapplinghook.items.alternategrapple.RocketDoubleMotorHook;
import com.yyon.grapplinghook.items.alternategrapple.RocketHook;
import com.yyon.grapplinghook.items.alternategrapple.SmartHook;
import com.yyon.grapplinghook.items.upgrades.BaseUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.DoubleUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.ForcefieldUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.LimitsUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.MagnetUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.MotorUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.RocketUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.RopeUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.StaffUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.SwingUpgradeItem;
import com.yyon.grapplinghook.items.upgrades.ThrowUpgradeItem;
import com.yyon.grapplinghook.network.DetachSingleHookMessage;
import com.yyon.grapplinghook.network.GrappleAttachMessage;
import com.yyon.grapplinghook.network.GrappleAttachPosMessage;
import com.yyon.grapplinghook.network.GrappleDetachMessage;
import com.yyon.grapplinghook.network.GrappleEndMessage;
import com.yyon.grapplinghook.network.GrappleModifierMessage;
import com.yyon.grapplinghook.network.KeypressMessage;
import com.yyon.grapplinghook.network.LoggedInMessage;
import com.yyon.grapplinghook.network.PlayerMovementMessage;
import com.yyon.grapplinghook.network.SegmentMessage;
import net.minecraft.block.Block;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.Enchantment.Rarity;
import net.minecraft.enchantment.EnumEnchantmentType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Config.Type;
import net.minecraftforge.common.config.ConfigManager;
import net.minecraftforge.common.util.EnumHelper;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import net.minecraftforge.fml.common.registry.EntityRegistry;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;
	}
	
	public static Item[] getAllItems() {
		return new Item[] {
				grapplebowitem, 
				itemBlockGrappleModifier,
				enderhookitem, 
				magnethookitem,
				rockethookitem,
				motorhookitem, 
				smarthookitem, 
				doublemotorhookitem, 
				rocketdoublemotorhookitem, 
				launcheritem, 
				repelleritem, 
				longfallboots, 
				baseupgradeitem, 
				ropeupgradeitem, 
				throwupgradeitem, 
				motorupgradeitem, 
				swingupgradeitem, 
				staffupgradeitem, 
				forcefieldupgradeitem, 
				magnetupgradeitem, 
				doubleupgradeitem, 
				rocketupgradeitem, 
				limitsupgradeitem, 
				};
	}
	
	@SubscribeEvent
	public void registerEnchantments(RegistryEvent.Register<Enchantment> event) {
	    event.getRegistry().registerAll(wallrunenchantment, doublejumpenchantment, slidingenchantment);

	}
	
	@EventHandler
	public void preInit(FMLPreInitializationEvent event){
//		System.out.println("PREINIT!!!");
		grapplebowitem = new grappleBow();
		grapplebowitem.setRegistryName("grapplinghook");
		motorhookitem = new MotorHook();
		motorhookitem.setRegistryName("motorhook");
		smarthookitem = new SmartHook();
		smarthookitem.setRegistryName("smarthook");
		doublemotorhookitem = new DoubleMotorHook();
		doublemotorhookitem.setRegistryName("doublemotorhook");
		rocketdoublemotorhookitem = new RocketDoubleMotorHook();
		rocketdoublemotorhookitem.setRegistryName("rocketdoublemotorhook");
		enderhookitem = new EnderHook();
		enderhookitem.setRegistryName("enderhook");
		magnethookitem = new MagnetHook();
		magnethookitem.setRegistryName("magnethook");
		rockethookitem = new RocketHook();
		rockethookitem.setRegistryName("rockethook");
		launcheritem = new launcherItem();
		launcheritem.setRegistryName("launcheritem");
		longfallboots = new LongFallBoots(ItemArmor.ArmorMaterial.DIAMOND, 3);
		longfallboots.setRegistryName("longfallboots");
		repelleritem = new repeller();
		repelleritem.setRegistryName("repeller");
	    baseupgradeitem = new BaseUpgradeItem();
	    baseupgradeitem.setRegistryName("baseupgradeitem");
	    doubleupgradeitem = new DoubleUpgradeItem();
	    doubleupgradeitem.setRegistryName("doubleupgradeitem");
	    doubleupgradeitem.setContainerItem(doubleupgradeitem);
	    forcefieldupgradeitem = new ForcefieldUpgradeItem();
	    forcefieldupgradeitem.setRegistryName("forcefieldupgradeitem");
	    forcefieldupgradeitem.setContainerItem(forcefieldupgradeitem);
	    magnetupgradeitem = new MagnetUpgradeItem();
	    magnetupgradeitem.setRegistryName("magnetupgradeitem");
	    magnetupgradeitem.setContainerItem(magnetupgradeitem);
	    motorupgradeitem = new MotorUpgradeItem();
	    motorupgradeitem.setRegistryName("motorupgradeitem");
	    motorupgradeitem.setContainerItem(motorupgradeitem);
	    ropeupgradeitem = new RopeUpgradeItem();
	    ropeupgradeitem.setRegistryName("ropeupgradeitem");
	    ropeupgradeitem.setContainerItem(ropeupgradeitem);
	    staffupgradeitem = new StaffUpgradeItem();
	    staffupgradeitem.setRegistryName("staffupgradeitem");
	    staffupgradeitem.setContainerItem(staffupgradeitem);
	    swingupgradeitem = new SwingUpgradeItem();
	    swingupgradeitem.setRegistryName("swingupgradeitem");
	    swingupgradeitem.setContainerItem(swingupgradeitem);
	    throwupgradeitem = new ThrowUpgradeItem();
	    throwupgradeitem.setRegistryName("throwupgradeitem");
	    throwupgradeitem.setContainerItem(throwupgradeitem);
	    limitsupgradeitem = new LimitsUpgradeItem();
	    limitsupgradeitem.setRegistryName("limitsupgradeitem");
	    limitsupgradeitem.setContainerItem(limitsupgradeitem);
	    rocketupgradeitem = new RocketUpgradeItem();
	    rocketupgradeitem.setRegistryName("rocketupgradeitem");
	    rocketupgradeitem.setContainerItem(rocketupgradeitem);
	    
	    wallrunenchantment = new WallrunEnchantment();
	    wallrunenchantment.setRegistryName("wallrunenchantment");
	    doublejumpenchantment = new DoublejumpEnchantment();
	    doublejumpenchantment.setRegistryName("doublejumpenchantment");
	    slidingenchantment = new SlidingEnchantment();
	    slidingenchantment.setRegistryName("slidingenchantment");
	    
//		System.out.println(grapplebowitem);
		
		resourceLocation = new ResourceLocation(grapplemod.MODID, "grapplemod");
		
		registerEntity(grappleArrow.class, "grappleArrow");
		
		network = NetworkRegistry.INSTANCE.newSimpleChannel("grapplemodchannel");
		byte id = 0;
		network.registerMessage(PlayerMovementMessage.Handler.class, PlayerMovementMessage.class, id++, Side.SERVER);
		network.registerMessage(GrappleAttachMessage.Handler.class, GrappleAttachMessage.class, id++, Side.CLIENT);
		network.registerMessage(GrappleEndMessage.Handler.class, GrappleEndMessage.class, id++, Side.SERVER);
		network.registerMessage(GrappleDetachMessage.Handler.class, GrappleDetachMessage.class, id++, Side.CLIENT);
		network.registerMessage(DetachSingleHookMessage.Handler.class, DetachSingleHookMessage.class, id++, Side.CLIENT);
		network.registerMessage(GrappleAttachPosMessage.Handler.class, GrappleAttachPosMessage.class, id++, Side.CLIENT);
		network.registerMessage(SegmentMessage.Handler.class, SegmentMessage.class, id++, Side.CLIENT);
		network.registerMessage(GrappleModifierMessage.Handler.class, GrappleModifierMessage.class, id++, Side.SERVER);
		network.registerMessage(LoggedInMessage.Handler.class, LoggedInMessage.class, id++, Side.CLIENT);
		network.registerMessage(KeypressMessage.Handler.class, KeypressMessage.class, id++, Side.SERVER);
		
		blockGrappleModifier = (BlockGrappleModifier)(new BlockGrappleModifier().setUnlocalizedName("block_grapple_modifier"));