package crazypants.enderio.teleport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovementInput;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import crazypants.enderio.EnderIO;
import crazypants.enderio.GuiHandler;
import crazypants.enderio.config.Config;
import crazypants.enderio.enderface.TileEnderIO;
import crazypants.enderio.network.PacketHandler;
import crazypants.enderio.teleport.packet.PacketDrainStaff;
import crazypants.enderio.teleport.packet.PacketOpenAuthGui;
import crazypants.enderio.teleport.packet.PacketTravelEvent;
import crazypants.util.BlockCoord;
import crazypants.util.Util;
import crazypants.vecmath.Camera;
import crazypants.vecmath.Matrix4d;
import crazypants.vecmath.VecmathUtil;
import crazypants.vecmath.Vector2d;
import crazypants.vecmath.Vector3d;
public class TravelController {
public static final TravelController instance = new TravelController();
private Random rand = new Random();
private boolean wasJumping = false;
private boolean showTargets = false;
BlockCoord onBlockCoord;
BlockCoord selectedCoord;
Camera currentView = new Camera();
private final HashMap<BlockCoord, Float> candidates = new HashMap<BlockCoord, Float>();
private boolean selectionEnabled = true;
private double fovRad;
private double tanFovRad;
private final List<UniqueIdentifier> blackList = new ArrayList<GameRegistry.UniqueIdentifier>();
private TravelController() {
String[] blackListNames = Config.travelStaffBlinkBlackList;
for(String name : blackListNames) {
blackList.add(new UniqueIdentifier(name));
}
}
public boolean activateTravelAccessable(ItemStack equipped, World world, EntityPlayer player, TravelSource source) {
if(!hasTarget()) {
return false;
}
BlockCoord target = selectedCoord;
TileEntity te = world.getTileEntity(target.x, target.y, target.z);
if(te instanceof ITravelAccessable) {
ITravelAccessable ta = (ITravelAccessable) te;
if(ta.getRequiresPassword(player)) {
PacketOpenAuthGui p = new PacketOpenAuthGui(target.x, target.y, target.z);
PacketHandler.INSTANCE.sendToServer(p);
return true;
}
}
if(isTargetEnderIO()) {
openEnderIO(equipped, world, player);
} else if(Config.travelAnchorEnabled) {
travelToSelectedTarget(player, source, false);
}
return true;
}
public boolean doBlink(ItemStack equipped, EntityPlayer player) {
Vector3d eye = Util.getEyePositionEio(player);
Vector3d look = Util.getLookVecEio(player);
Vector3d sample = new Vector3d(look);
sample.scale(Config.travelStaffMaxBlinkDistance);
sample.add(eye);
Vec3 eye3 = Vec3.createVectorHelper(eye.x, eye.y, eye.z);
Vec3 end = Vec3.createVectorHelper(sample.x, sample.y, sample.z);
double playerHeight = player.yOffset;
//if you looking at you feet, and your player height to the max distance, or part there of
double lookComp = -look.y * playerHeight;
double maxDistance = Config.travelStaffMaxBlinkDistance + lookComp;
MovingObjectPosition p = player.worldObj.rayTraceBlocks(eye3, end, !Config.travelStaffBlinkThroughClearBlocksEnabled);
if(p == null) {
//go as far as possible
for (double i = maxDistance; i > 1; i--) {
sample.set(look);
sample.scale(i);
sample.add(eye);
//we test against our feets location
sample.y -= playerHeight;
if(doBlinkAround(player, sample, true)) {
return true;
}
}
return false;
} else {
List<MovingObjectPosition> res = Util.raytraceAll(player.worldObj, eye3, end, !Config.travelStaffBlinkThroughClearBlocksEnabled);
for (MovingObjectPosition pos : res) {
if(pos != null) {
Block hitBlock = player.worldObj.getBlock(pos.blockX, pos.blockY, pos.blockZ);
if(isBlackListedBlock(player, pos, hitBlock)) {
maxDistance = Math.min(maxDistance, VecmathUtil.distance(eye, new Vector3d(pos.blockX + 0.5, pos.blockY + 0.5, pos.blockZ + 0.5)) - 1.5 - lookComp);
}
}
}
eye3 = Vec3.createVectorHelper(eye.x, eye.y, eye.z);
Vector3d targetBc = new Vector3d(p.blockX, p.blockY, p.blockZ);
double sampleDistance = 1.5;
double teleDistance = VecmathUtil.distance(eye, new Vector3d(p.blockX + 0.5, p.blockY + 0.5, p.blockZ + 0.5)) + sampleDistance;
while (teleDistance < maxDistance) {
sample.set(look);
sample.scale(sampleDistance);
sample.add(targetBc);
//we test against our feets location
sample.y -= playerHeight;
if(doBlinkAround(player, sample, false)) {
return true;
}
teleDistance++;
sampleDistance++;
}
sampleDistance = -0.5;
teleDistance = VecmathUtil.distance(eye, new Vector3d(p.blockX + 0.5, p.blockY + 0.5, p.blockZ + 0.5)) + sampleDistance;
while (teleDistance > 1) {
sample.set(look);
sample.scale(sampleDistance);
sample.add(targetBc);
//we test against our feets location
sample.y -= playerHeight;
if(doBlinkAround(player, sample, false)) {
return true;
}
sampleDistance--;
teleDistance--;
}
}
return false;
}
private boolean isBlackListedBlock(EntityPlayer player, MovingObjectPosition pos, Block hitBlock) {
UniqueIdentifier ui = GameRegistry.findUniqueIdentifierFor(hitBlock);
if(ui == null) {
return false;
}
return blackList.contains(ui);
}
private boolean doBlinkAround(EntityPlayer player, Vector3d sample, boolean conserveMomentum) {
if(doBlink(player, new BlockCoord((int) Math.round(sample.x), (int) Math.round(sample.y) - 1, (int) Math.round(sample.z)), conserveMomentum)) {
return true;
}
if(doBlink(player, new BlockCoord((int) Math.round(sample.x), (int) Math.round(sample.y), (int) Math.round(sample.z)), conserveMomentum)) {
return true;
}
if(doBlink(player, new BlockCoord((int) Math.round(sample.x), (int) Math.round(sample.y) + 1, (int) Math.round(sample.z)), conserveMomentum)) {
return true;
}
return false;
}
private boolean doBlink(EntityPlayer player, BlockCoord coord, boolean conserveMomentum) {
return travelToLocation(player, TravelSource.STAFF_BLINK, coord, conserveMomentum);
}
public boolean showTargets() {
return showTargets && selectionEnabled;
}
public void setSelectionEnabled(boolean b) {
selectionEnabled = b;
if(!selectionEnabled) {
candidates.clear();
}
}
public boolean isBlockSelected(BlockCoord coord) {
if(coord == null) {
return false;
}
return coord.equals(selectedCoord);
}
public void addCandidate(BlockCoord coord) {
if(!candidates.containsKey(coord)) {
candidates.put(coord, -1f);
}
}
public int getMaxTravelDistanceSq() {
return TravelSource.getMaxDistanceSq();
}
public boolean isTargetEnderIO() {
if(selectedCoord == null) {
return false;
}
return EnderIO.instance.proxy.getClientPlayer().worldObj.getBlock(selectedCoord.x, selectedCoord.y, selectedCoord.z) == EnderIO.blockEnderIo;
}
@SubscribeEvent
public void onRender(RenderWorldLastEvent event) {
Minecraft mc = Minecraft.getMinecraft();
Vector3d eye = Util.getEyePositionEio(mc.thePlayer);
Vector3d lookAt = Util.getLookVecEio(mc.thePlayer);
lookAt.add(eye);
Matrix4d mv = VecmathUtil.createMatrixAsLookAt(eye, lookAt, new Vector3d(0, 1, 0));
float fov = Minecraft.getMinecraft().gameSettings.fovSetting;
Matrix4d pr = VecmathUtil.createProjectionMatrixAsPerspective(fov, 0.05f, mc.gameSettings.renderDistanceChunks * 16, mc.displayWidth,
mc.displayHeight);
currentView.setProjectionMatrix(pr);
currentView.setViewMatrix(mv);
currentView.setViewport(0, 0, mc.displayWidth, mc.displayHeight);
fovRad = Math.toRadians(fov);
tanFovRad = Math.tanh(fovRad);
}
@SideOnly(Side.CLIENT)
@SubscribeEvent
public void onClientTick(TickEvent.ClientTickEvent event) {
if(event.phase == TickEvent.Phase.END) {
EntityClientPlayerMP player = Minecraft.getMinecraft().thePlayer;
if(player == null) {
return;
}
onBlockCoord = getActiveTravelBlock(player);
boolean onBlock = onBlockCoord != null;
showTargets = onBlock || isTravelItemActive(player);
if(showTargets) {
updateSelectedTarget(player);
} else {
selectedCoord = null;
}
MovementInput input = player.movementInput;
if(input.jump && !wasJumping && onBlock && selectedCoord != null) {
BlockCoord target = TravelController.instance.selectedCoord;
TileEntity te = player.worldObj.getTileEntity(target.x, target.y, target.z);
if(te instanceof ITravelAccessable) {
ITravelAccessable ta = (ITravelAccessable) te;
if(ta.getRequiresPassword(player)) {
PacketOpenAuthGui p = new PacketOpenAuthGui(target.x, target.y, target.z);
PacketHandler.INSTANCE.sendToServer(p);
return;
}
}
if(isTargetEnderIO()) {
openEnderIO(null, player.worldObj, player);
} else if(Config.travelAnchorEnabled && travelToSelectedTarget(player, TravelSource.BLOCK, false)) {
input.jump = false;
try{
ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class, (EntityPlayer)player, 0, "flyToggleTimer", "field_71101_bC");
} catch (Exception e) {
//ignore
}
}
}
wasJumping = input.jump;
candidates.clear();
}
}
public boolean hasTarget() {
return selectedCoord != null;
}
public void openEnderIO(ItemStack equipped, World world, EntityPlayer player) {
BlockCoord target = TravelController.instance.selectedCoord;
TileEntity te = world.getTileEntity(target.x, target.y, target.z);
if(!(te instanceof TileEnderIO)) {
return;
}
TileEnderIO eio = (TileEnderIO) te;
if(eio.canBlockBeAccessed(player)) {
int requiredPower = equipped == null ? 0 : instance.getRequiredPower(player, TravelSource.STAFF, target);
if(requiredPower <= 0 || requiredPower <= getEnergyInTravelItem(equipped)) {
if(requiredPower > 0) {
PacketDrainStaff p = new PacketDrainStaff(requiredPower);
PacketHandler.INSTANCE.sendToServer(p);
}
player.openGui(EnderIO.instance, GuiHandler.GUI_ID_ENDERFACE, world, target.x,
TravelController.instance.selectedCoord.y, TravelController.instance.selectedCoord.z);
}
} else {
player.addChatComponentMessage(new ChatComponentTranslation("enderio.gui.travelAccessable.unauthorised"));
}
}
public int getEnergyInTravelItem(ItemStack equipped) {
if(equipped == null || !(equipped.getItem() instanceof IItemOfTravel)) {
return 0;
}
return ((IItemOfTravel) equipped.getItem()).getEnergyStored(equipped);
}
public boolean isTravelItemActive(EntityPlayer ep) {
if(ep == null || ep.getCurrentEquippedItem() == null) {
return false;
}
ItemStack equipped = ep.getCurrentEquippedItem();
if(equipped.getItem() instanceof IItemOfTravel) {
return ((IItemOfTravel) equipped.getItem()).isActive(ep, equipped);
}
return false;
}
public boolean travelToSelectedTarget(EntityPlayer player, TravelSource source,boolean conserveMomentum) {
return travelToLocation(player, source, selectedCoord, conserveMomentum);
}
public boolean travelToLocation(EntityPlayer player, TravelSource source, BlockCoord coord, boolean conserveMomentum) {
if(source != TravelSource.STAFF_BLINK) {
TileEntity te = player.worldObj.getTileEntity(coord.x, coord.y, coord.z);
if(te instanceof ITravelAccessable) {
ITravelAccessable ta = (ITravelAccessable) te;
if(!ta.canBlockBeAccessed(player)) {
player.addChatComponentMessage(new ChatComponentTranslation("enderio.gui.travelAccessable.unauthorised"));
return false;
}
}
}
int requiredPower = 0;
requiredPower = getRequiredPower(player, source, coord);
if(requiredPower < 0) {
return false;
}
if(!isInRangeTarget(player, coord, source.maxDistanceTravelledSq)) {
if(source != TravelSource.STAFF_BLINK) {
player.addChatComponentMessage(new ChatComponentTranslation("enderio.blockTravelPlatform.outOfRange"));
}
return false;
}
if(!isValidTarget(player, coord, source)) {
if(source != TravelSource.STAFF_BLINK) {
player.addChatComponentMessage(new ChatComponentTranslation("enderio.blockTravelPlatform.invalidTarget"));
}
return false;
}
sendTravelEvent(coord, source, requiredPower, conserveMomentum);
for (int i = 0; i < 6; ++i) {
player.worldObj.spawnParticle("portal", player.posX + (rand.nextDouble() - 0.5D), player.posY + rand.nextDouble() * player.height - 0.25D,
player.posZ + (rand.nextDouble() - 0.5D), (this.rand.nextDouble() - 0.5D) * 2.0D, -rand.nextDouble(),
(rand.nextDouble() - 0.5D) * 2.0D);
}
return true;
}
public int getRequiredPower(EntityPlayer player, TravelSource source, BlockCoord coord) {
if(!isTravelItemActive(player)) {
return 0;
}
int requiredPower;
ItemStack staff = player.getCurrentEquippedItem();
requiredPower = (int) (getDistance(player, coord) * source.powerCostPerBlockTraveledRF);
int canUsePower = getEnergyInTravelItem(staff);
if(requiredPower > canUsePower) {
player.addChatComponentMessage(new ChatComponentTranslation("enderio.itemTravelStaff.notEnoughPower"));
return -1;
}
return requiredPower;
}
private boolean isInRangeTarget(EntityPlayer player, BlockCoord bc, float maxSq) {
return getDistanceSquared(player, bc) <= maxSq;
}
private double getDistanceSquared(EntityPlayer player, BlockCoord bc) {
if(player == null || bc == null) {
return 0;
}
Vector3d eye = Util.getEyePositionEio(player);
Vector3d target = new Vector3d(bc.x + 0.5, bc.y + 0.5, bc.z + 0.5);
return eye.distanceSquared(target);
}
private double getDistance(EntityPlayer player, BlockCoord coord) {
return Math.sqrt(getDistanceSquared(player, coord));
}
private boolean isValidTarget(EntityPlayer player, BlockCoord bc, TravelSource source) {
if(bc == null) {
return false;
}
World w = player.worldObj;
BlockCoord baseLoc = bc;
if(source != TravelSource.STAFF_BLINK) {
//targeting a block so go one up
baseLoc = bc.getLocation(ForgeDirection.UP);
}
return canTeleportTo(player, source, baseLoc, w)
&& canTeleportTo(player, source, baseLoc.getLocation(ForgeDirection.UP), w);
}
private boolean canTeleportTo(EntityPlayer player, TravelSource source, BlockCoord bc, World w) {
if(bc.y < 1) {
return false;
}
if(source == TravelSource.STAFF_BLINK && !Config.travelStaffBlinkThroughSolidBlocksEnabled) {
Vec3 start = Util.getEyePosition(player);
Vec3 target = Vec3.createVectorHelper(bc.x + 0.5f, bc.y + 0.5f, bc.z + 0.5f);
if(!canBlinkTo(bc, w, start, target)) {
return false;
}
}
Block block = w.getBlock(bc.x, bc.y, bc.z);
if(block == null || block.isAir(w, bc.x, bc.y, bc.z)) {
return true;
}
final AxisAlignedBB aabb = block.getCollisionBoundingBoxFromPool(w, bc.x, bc.y, bc.z);
return aabb == null || aabb.getAverageEdgeLength() < 0.7;
}
private boolean canBlinkTo(BlockCoord bc, World w, Vec3 start, Vec3 target) {
MovingObjectPosition p = w.rayTraceBlocks(start, target, !Config.travelStaffBlinkThroughClearBlocksEnabled);
if(p != null) {
if(!Config.travelStaffBlinkThroughClearBlocksEnabled) {
return false;
}
Block block = w.getBlock(p.blockX, p.blockY, p.blockZ);
if(isClear(w, block, p.blockX, p.blockY, p.blockZ)) {
if(new BlockCoord(p.blockX, p.blockY, p.blockZ).equals(bc)) {
return true;
}
//need to step
Vector3d sv = new Vector3d(start.xCoord, start.yCoord, start.zCoord);
Vector3d rayDir = new Vector3d(target.xCoord, target.yCoord, target.zCoord);
rayDir.sub(sv);
rayDir.normalize();
rayDir.add(sv);
return canBlinkTo(bc, w, Vec3.createVectorHelper(rayDir.x, rayDir.y, rayDir.z), target);
} else {
return false;
}
}
return true;
}
private boolean isClear(World w, Block block, int x, int y, int z) {
if(block == null || block.isAir(w, x, y, z)) {
return true;
}
final AxisAlignedBB aabb = block.getCollisionBoundingBoxFromPool(w, x, y, z);
if(aabb == null || aabb.getAverageEdgeLength() < 0.7) {
return true;
}
return block.getLightOpacity(w, x, y, z) < 2;
}
@SideOnly(Side.CLIENT)
private void updateSelectedTarget(EntityClientPlayerMP player) {
selectedCoord = null;
if(candidates.isEmpty()) {
return;
}
double closestDistance = Double.MAX_VALUE;
for (BlockCoord bc : candidates.keySet()) {
if(!bc.equals(onBlockCoord)) {
double d = addRatio(bc);
if(d < closestDistance) {
selectedCoord = bc;
closestDistance = d;
}
}
}
if(selectedCoord != null) {
Vector3d blockCenter = new Vector3d(selectedCoord.x + 0.5, selectedCoord.y + 0.5, selectedCoord.z + 0.5);
Vector2d blockCenterPixel = currentView.getScreenPoint(blockCenter);
Vector2d screenMidPixel = new Vector2d(Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight);
screenMidPixel.scale(0.5);
double pixDist = blockCenterPixel.distance(screenMidPixel);
double rat = pixDist / Minecraft.getMinecraft().displayHeight;
if(rat != rat) {
rat = 0;
}
if(rat > 0.07) {
selectedCoord = null;
}
}
}
public double getScaleForCandidate(Vector3d loc) {
BlockCoord bc = new BlockCoord((int) loc.x, (int) loc.y, (int) loc.z);
float ratio = -1;
Float r = candidates.get(bc);
if(r != null) {
ratio = r;
}
if(ratio < 0) {
//no cached value
addRatio(bc);
ratio = candidates.get(bc);
}
//smoothly zoom to a larger size, starting when the point is the middle 20% of the screen
float start = 0.2f;
float end = 0.01f;
double mix = MathHelper.clamp_float((start - ratio) / (start - end), 0, 1);
double scale = 1;
if(mix > 0) {
Vector3d eyePoint = Util.getEyePositionEio(EnderIO.proxy.getClientPlayer());
scale = tanFovRad * eyePoint.distance(loc);
//Using this scale will give us the block full screen, we will make it 20% of the screen
scale *= Config.travelAnchorZoomScale;
//only apply 70% of the scaling so more distance targets are still smaller than closer targets
float nf = 1 - MathHelper.clamp_float((float) eyePoint.distanceSquared(loc) / TravelSource.STAFF.maxDistanceTravelledSq, 0, 1);
scale = scale * (0.3 + 0.7 * nf);
scale = (scale * mix) + (1 - mix);
scale = Math.max(1, scale);
}
return scale;
}
private double addRatio(BlockCoord bc) {
Vector2d sp = currentView.getScreenPoint(new Vector3d(bc.x + 0.5, bc.y + 0.5, bc.z + 0.5));
Vector2d mid = new Vector2d(Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight);
mid.scale(0.5);
double d = sp.distance(mid);
if(d != d) {
d = 0f;
}
float ratio = (float) d / Minecraft.getMinecraft().displayWidth;
candidates.put(bc, ratio);
return d;
}
@SideOnly(Side.CLIENT)
private int getMaxTravelDistanceSqForPlayer(EntityClientPlayerMP player) {
if(isTravelItemActive(player)) {
return TravelSource.STAFF.maxDistanceTravelledSq;
}
return TravelSource.BLOCK.maxDistanceTravelledSq;
}
private void sendTravelEvent(BlockCoord bc, TravelSource source, int powerUse, boolean conserveMomentum) {
PacketTravelEvent p = new PacketTravelEvent(bc.x, bc.y, bc.z, powerUse, conserveMomentum);
PacketHandler.INSTANCE.sendToServer(p);
}
@SideOnly(Side.CLIENT)
private BlockCoord getActiveTravelBlock(EntityClientPlayerMP player) {
World world = Minecraft.getMinecraft().theWorld;
if(world != null && player != null) {
int x = MathHelper.floor_double(player.posX);
int y = MathHelper.floor_double(player.boundingBox.minY) - 1;
int z = MathHelper.floor_double(player.posZ);
if(world.getBlock(x, y, z) == EnderIO.blockTravelPlatform) {
return new BlockCoord(x, y, z);
}
}
return null;
}
}