public interface IApiInterface {} public class BlockGlassesBridge extends BlockOP {

	public BlockGlassesBridge() {
		super(Material.ground);
		setRenderMode(RenderMode.BLOCK_ONLY);
	}

	@Override
	public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) {
		if (player.isSneaking() || world.isRemote) return false;

		final TileEntityGlassesBridge te = getTileEntity(world, x, y, z, TileEntityGlassesBridge.class);
		if (te == null) return false;

		return TerminalIdAccess.instance.setIdFor(player, te.getOrCreateGuid());
	}
} @NetworkEventMeta(direction = EventDirection.S2C)
public static class GlassesChangeBackgroundEvent extends GlassesEvent {

	@Serialize
	public int backgroundColor;

	public GlassesChangeBackgroundEvent(long guid, int backgroundColor) {
		super(guid);
		this.backgroundColor = backgroundColor;
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesComponentMouseButtonEvent extends GlassesComponentMouseEvent {
	@Serialize
	public int button;

	@Serialize
	public boolean pressed;

	public GlassesComponentMouseButtonEvent(long guid, int componentId, SurfaceType surfaceType, float x, float y, int button, boolean pressed) {
		super(guid, componentId, surfaceType, x, y);
		this.button = button;
		this.pressed = pressed;
	}

	@Override
	public String getEventName() {
		return pressed? "glasses_component_mouse_down" : "glasses_component_mouse_up";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(access.createIndex(componentId), surfaceType.scriptValue, x, y, button);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesComponentMouseWheelEvent extends GlassesComponentMouseEvent {
	@Serialize
	public int wheel;

	public GlassesComponentMouseWheelEvent(long guid, int componentId, SurfaceType surfaceType, float x, float y, int wheel) {
		super(guid, componentId, surfaceType, x, y);
		this.wheel = wheel;
	}

	@Override
	public String getEventName() {
		return "glasses_component_mouse_wheel";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(access.createIndex(componentId), surfaceType.scriptValue, x, y, wheel);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesKeyDownEvent extends GlassesClientEvent {
	@Serialize
	public char ch;

	@Serialize
	public int code;

	@Serialize
	public boolean isRepeat;

	public GlassesKeyDownEvent(long guid, char ch, int code, boolean isRepeat) {
		super(guid);
		this.ch = ch;
		this.code = code;
		this.isRepeat = isRepeat;
	}

	@Override
	public String getEventName() {
		return "glasses_key_down";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		// CC has some problems with chars
		return wrap(code, Character.toString(ch), isRepeat);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesKeyUpEvent extends GlassesClientEvent {

	@Serialize
	public int code;

	public GlassesKeyUpEvent(long guid, int code) {
		super(guid);
		this.code = code;
	}

	@Override
	public String getEventName() {
		return "glasses_key_up";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(code);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesMouseButtonEvent extends GlassesClientEvent {
	@Serialize
	public int button;

	@Serialize
	public boolean pressed;

	public GlassesMouseButtonEvent(long guid, int button, boolean pressed) {
		super(guid);
		this.button = button;
		this.pressed = pressed;
	}

	@Override
	public String getEventName() {
		return pressed? "glasses_mouse_down" : "glasses_mouse_up";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(button);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesMouseDragEvent extends GlassesClientEvent {

	@Serialize
	public float dx;

	@Serialize
	public float dy;

	public GlassesMouseDragEvent(long guid, float dx, float dy) {
		super(guid);
		this.dx = dx;
		this.dy = dy;
	}

	@Override
	public String getEventName() {
		return "glasses_mouse_drag";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(dx, dy);
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesMouseWheelEvent extends GlassesClientEvent {
	@Serialize
	public int wheel;

	public GlassesMouseWheelEvent(long guid, int wheel) {
		super(guid);
		this.wheel = wheel;
	}

	@Override
	public String getEventName() {
		return "glasses_mouse_scroll";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return wrap(wheel);
	}
} @NetworkEventMeta(direction = EventDirection.S2C)
public static class GlassesSetDragParamsEvent extends GlassesEvent {

	@Serialize
	public int period;

	@Serialize
	public int threshold;

	public GlassesSetDragParamsEvent(long guid, int period, int threshold) {
		super(guid);
		this.period = period;
		this.threshold = threshold;
	}

} @NetworkEventMeta(direction = EventDirection.S2C)
public static class GlassesSetGuiVisibilityEvent extends GlassesEvent {

	@Serialize
	public Map<GuiElements, Boolean> visibility;

	public GlassesSetGuiVisibilityEvent(long guid, Map<GuiElements, Boolean> visibility) {
		super(guid);
		this.visibility = visibility;
	}
} @NetworkEventMeta(direction = EventDirection.S2C)
public static class GlassesSetKeyRepeatEvent extends GlassesEvent {

	@Serialize
	public boolean repeat;

	public GlassesSetKeyRepeatEvent(long guid, boolean repeat) {
		super(guid);
		this.repeat = repeat;
	}
} @NetworkEventMeta(direction = EventDirection.C2S)
public static class GlassesSignalCaptureEvent extends GlassesClientEvent {
	@Serialize
	public boolean captureState;

	public GlassesSignalCaptureEvent(long guid, boolean captureState) {
		super(guid);
		this.captureState = captureState;
	}

	@Override
	public String getEventName() {
		return captureState? "glasses_capture" : "glasses_release";
	}

	@Override
	public Object[] getEventArgs(IArchitecture access) {
		return NO_EXTRAS;
	}
} @NetworkEventMeta(direction = EventDirection.S2C)
public static class GlassesStopCaptureEvent extends GlassesEvent {

	public GlassesStopCaptureEvent(long guid) {
		super(guid);
	}
} public class ItemGlasses extends ItemArmor implements ITerminalItem {
	private static final String OPENP_TAG = "openp";

	public ItemGlasses() {
		super(ArmorMaterial.CHAIN, 0, 0);
		setMaxDamage(0);
		setMaxStackSize(1);
		setHasSubtypes(true);
		setCreativeTab(OpenPeripheralAddons.tabOpenPeripheralAddons);
	}

	private static Long extractGuid(ItemStack stack) {
		NBTTagCompound tag = ItemUtils.getItemTag(stack);
		if (!tag.hasKey(OPENP_TAG)) return null;

		NBTTagCompound openp = tag.getCompoundTag(OPENP_TAG);
		return TerminalUtils.extractGuid(openp);
	}

	@Override
	@SideOnly(Side.CLIENT)
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void addInformation(ItemStack itemStack, EntityPlayer player, List list, boolean par4) {
		Long guid = extractGuid(itemStack);
		if (guid != null) list.add(StatCollector.translateToLocalFormatted("openperipheral.misc.key", TerminalUtils.formatTerminalId(guid)));
	}

	@Override
	public String getArmorTexture(ItemStack stack, Entity entity, int slot, String type) {
		switch (MiscUtils.getHoliday()) {
			case 1:
				return "openperipheraladdons:textures/models/glasses_valentines.png";
			case 2:
				return "openperipheraladdons:textures/models/glasses_halloween.png";
			case 3:
				return "openperipheraladdons:textures/models/glasses_christmas.png";
			default:
				return "openperipheraladdons:textures/models/glasses.png";
		}
	}

	@Override
	public Long getTerminalGuid(ItemStack stack) {
		return extractGuid(stack);
	}

	@Override
	public void bindToTerminal(ItemStack stack, long guid) {
		NBTTagCompound tag = ItemUtils.getItemTag(stack);

		NBTTagCompound openPTag = (NBTTagCompound)tag.getTag(OPENP_TAG);
		if (openPTag == null) {
			openPTag = new NBTTagCompound();
			tag.setTag(OPENP_TAG, openPTag);
		}

		openPTag.setLong("guid", guid);
	}

} public class ItemGlassesBridge extends ItemOpenBlock {

	public ItemGlassesBridge(Block block) {
		super(block);
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void addInformation(ItemStack stack, EntityPlayer player, List result, boolean extended) {
		NBTTagCompound tag = stack.getTagCompound();

		if (tag != null && tag.hasKey(TileEntityGlassesBridge.TAG_GUID)) {
			long guid = tag.getLong(TileEntityGlassesBridge.TAG_GUID);
			result.add(StatCollector.translateToLocalFormatted("openperipheral.misc.key", TerminalUtils.formatTerminalId(guid)));
		}
	}

} public class ItemKeyboard extends Item {

	public ItemKeyboard() {
		setCreativeTab(OpenPeripheralAddons.tabOpenPeripheralAddons);
		setMaxStackSize(1);
	}

	@Override
	public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
		if (world.isRemote) {
			final Optional<Long> terminalGuid = TerminalIdAccess.instance.getIdFrom(player);
			if (terminalGuid.isPresent()) {
				FMLCommonHandler.instance().showGuiScreen(new GuiCapture(terminalGuid.get()));
			} else {
				player.addChatMessage(new ChatComponentTranslation("openperipheral.misc.no_glasses"));
			}
		}

		return stack;
	}
} public class ItemTerminalMetaProvider extends ItemStackMetaProviderSimple<ITerminalItem> {

	@Override
	public String getKey() {
		return "op_terminal";
	}

	@Override
	public Object getMeta(ITerminalItem target, ItemStack stack) {
		Map<String, Object> results = Maps.newHashMap();
		final Long terminalGuid = target.getTerminalGuid(stack);
		if (terminalGuid != null) results.put("bridge_name", TerminalUtils.formatTerminalId(terminalGuid));
		return results;
	}
} public class NbtGuidProviders {

	private static final String MAIN_TAG = "OPA-Terminal";

	private static final String GUID_TAG = "Guid";

	public static class NbtGetter extends HelmetGetterAdapter {
		@Override
		public Optional<Long> getFor(ItemStack stack) {
			return getTerminalGuid(stack);
		}
	}

	public static class NbtSetter extends HandSetterAdapter {
		@Override
		public boolean setFor(ItemStack stack, long guid) {
			final NBTTagCompound itemTag = stack.getTagCompound();
			if (itemTag != null && itemTag.hasKey(MAIN_TAG, Constants.NBT.TAG_COMPOUND)) {
				final NBTTagCompound terminalTag = itemTag.getCompoundTag(MAIN_TAG);
				terminalTag.setLong(GUID_TAG, guid);
				return true;
			}

			return false;
		}
	}

	public static NBTTagCompound getOrCreateTerminalTag(ItemStack stack) {
		final NBTTagCompound itemTag = ItemUtils.getItemTag(stack);
		if (!itemTag.hasKey(MAIN_TAG, Constants.NBT.TAG_COMPOUND)) {
			final NBTTagCompound result = new NBTTagCompound();
			itemTag.setTag(MAIN_TAG, result);
			return result;
		} else {
			return itemTag.getCompoundTag(MAIN_TAG);
		}
	}

	public static boolean hasTerminalCapabilities(ItemStack stack) {
		final NBTTagCompound itemTag = stack.getTagCompound();
		if (itemTag == null) return false;
		return itemTag.hasKey(MAIN_TAG, Constants.NBT.TAG_COMPOUND);
	}

	public static Optional<Long> getTerminalGuid(ItemStack stack) {
		final NBTTagCompound itemTag = stack.getTagCompound();
		if (itemTag != null && itemTag.hasKey(MAIN_TAG, Constants.NBT.TAG_COMPOUND)) {
			final NBTTagCompound terminalTag = itemTag.getCompoundTag(MAIN_TAG);
			if (terminalTag.hasKey(GUID_TAG, Constants.NBT.TAG_ANY_NUMERIC)) return Optional.of(terminalTag.getLong(GUID_TAG));
		}

		return Optional.absent();
	}

	public static void setTerminalGuid(ItemStack stack, Optional<Long> guid) {
		final NBTTagCompound terminalTag = getOrCreateTerminalTag(stack);
		if (guid.isPresent()) terminalTag.setLong(GUID_TAG, guid.get());
	}

} public class NbtTerminalMetaProvider implements IItemStackCustomMetaProvider<Item> {

	@Override
	public Class<? extends Item> getTargetClass() {
		return Item.class;
	}

	@Override
	public String getKey() {
		return "op_terminal_embedded";
	}

	@Override
	public boolean canApply(Item target, ItemStack stack) {
		return NbtGuidProviders.hasTerminalCapabilities(stack);
	}

	@Override
	public Object getMeta(Item target, ItemStack stack) {
		Map<String, Object> results = Maps.newHashMap();
		final Optional<Long> terminalGuid = NbtGuidProviders.getTerminalGuid(stack);
		if (terminalGuid.isPresent()) results.put("bridge_name", TerminalUtils.formatTerminalId(terminalGuid.get()));
		return results;
	}

} public abstract class TerminalEvent extends NetworkEvent {

	public long terminalId;

	public TerminalEvent() {}

	public TerminalEvent(long terminalId) {
		this.terminalId = terminalId;
	}

	@Override
	protected void readFromStream(DataInput input) throws IOException {
		terminalId = input.readLong();
	}

	@Override
	protected void writeToStream(DataOutput output) throws IOException {
		output.writeLong(terminalId);
	}

	@Override
	protected void appendLogInfo(List<String> info) {
		info.add(TerminalUtils.formatTerminalId(terminalId));
	}

	public abstract SurfaceType getSurfaceType();

	public abstract static class Reset extends TerminalEvent {
		public Reset() {
			super();
		}

		public Reset(long terminalId) {
			super(terminalId);
		}
	}

	public abstract static class DrawableReset extends Reset {
		public DrawableReset() {}

		public DrawableReset(long terminalId) {
			super(terminalId);
		}
	}

	@NetworkEventMeta(direction = EventDirection.C2S)
	public static class PublicDrawableReset extends DrawableReset {
		public PublicDrawableReset() {}

		public PublicDrawableReset(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.GLOBAL;
		}
	}

	@NetworkEventMeta(direction = EventDirection.C2S)
	public static class PublicStructureReset extends StructureReset {
		public PublicStructureReset() {}

		public PublicStructureReset(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.GLOBAL;
		}
	}

	public abstract static class StructureReset extends Reset {
		public StructureReset() {}

		public StructureReset(long terminalId) {
			super(terminalId);
		}
	}

	@NetworkEventMeta(direction = EventDirection.C2S)
	public static class PrivateDrawableReset extends DrawableReset {
		public PrivateDrawableReset() {}

		public PrivateDrawableReset(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.PRIVATE;
		}
	}

	@NetworkEventMeta(direction = EventDirection.C2S)
	public static class PrivateStructureReset extends StructureReset {
		public PrivateStructureReset() {}

		public PrivateStructureReset(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.PRIVATE;
		}
	}

	public abstract static class Clear extends TerminalEvent {
		public Clear() {}

		public Clear(long terminalId) {
			super(terminalId);
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C)
	public static class PrivateClear extends Clear {
		public PrivateClear() {}

		public PrivateClear(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.PRIVATE;
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C)
	public static class PublicClear extends Clear {
		public PublicClear() {}

		public PublicClear(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.GLOBAL;
		}
	}

	public abstract static class Data extends TerminalEvent {
		public final CommandList commands = new CommandList();

		public Data() {}

		public Data(long terminalId) {
			super(terminalId);
		}

		@Override
		protected void readFromStream(DataInput input) throws IOException {
			super.readFromStream(input);
			commands.readFromStream(input);
		}

		@Override
		protected void writeToStream(DataOutput output) throws IOException {
			super.writeToStream(output);
			commands.writeToStream(output);
		}

		@Override
		protected void appendLogInfo(List<String> info) {
			super.appendLogInfo(info);
			info.add(Integer.toString(commands.size()));
		}

		public SurfaceClient createSurface() {
			throw new AbstractMethodError();
		}
	}

	public abstract static class DrawableData extends Data {

		public DrawableData() {}

		public DrawableData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceClient createSurface() {
			return SurfaceClient.createPrivateSurface(terminalId);
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C, chunked = true, compressed = true)
	public static class PrivateDrawableData extends DrawableData {

		public PrivateDrawableData() {}

		public PrivateDrawableData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.PRIVATE;
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C, chunked = true, compressed = true)
	public static class PrivateStructureData extends StructureData {
		public PrivateStructureData() {}

		public PrivateStructureData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.PRIVATE;
		}
	}

	public abstract static class StructureData extends Data {
		public StructureData() {}

		public StructureData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceClient createSurface() {
			return SurfaceClient.createPublicSurface(terminalId);
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C, chunked = true, compressed = true)
	public static class PublicStructureData extends StructureData {
		public PublicStructureData() {}

		public PublicStructureData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.GLOBAL;
		}
	}

	@NetworkEventMeta(direction = EventDirection.S2C, chunked = true, compressed = true)
	public static class PublicDrawableData extends DrawableData {

		public PublicDrawableData() {}

		public PublicDrawableData(long terminalId) {
			super(terminalId);
		}

		@Override
		public SurfaceType getSurfaceType() {
			return SurfaceType.GLOBAL;
		}
	}

} @ApiSingleton
public class TerminalIdAccess implements ITerminalIdAccess {

	abstract static class HelmetGetterAdapter implements ITerminalIdGetter {
		@Override
		public Optional<Long> getFor(EntityPlayer player) {
			final ItemStack stack = TerminalUtils.getHeadSlot(player);
			return stack != null? getFor(stack) : Optional.<Long> absent();
		}

		protected abstract Optional<Long> getFor(ItemStack helmet);

		@Override
		public int getPriority() {
			return 0;
		}
	}

	abstract static class HandSetterAdapter implements ITerminalIdSetter {
		@Override
		public boolean setFor(EntityPlayer player, long guid) {
			final ItemStack stack = player.getHeldItem();
			return stack != null? setFor(stack, guid) : false;
		}

		protected abstract boolean setFor(ItemStack helmet, long guid);

		@Override
		public int getPriority() {
			return 0;
		}
	}

	public static class InterfaceGetter extends HelmetGetterAdapter {
		@Override
		public Optional<Long> getFor(ItemStack stack) {
			final Item item = stack.getItem();
			if (item instanceof ITerminalItem) {
				final ITerminalItem terminalItem = (ITerminalItem)item;
				return Optional.fromNullable(terminalItem.getTerminalGuid(stack));
			}

			return Optional.absent();
		}
	}

	public static class InterfaceSetter extends HandSetterAdapter {
		@Override
		public boolean setFor(ItemStack stack, long guid) {
			final Item item = stack.getItem();
			if (item instanceof ITerminalItem) {
				final ITerminalItem terminalItem = (ITerminalItem)item;
				terminalItem.bindToTerminal(stack, guid);
				return true;
			}

			return false;
		}
	}

	public static final TerminalIdAccess instance = new TerminalIdAccess();

	private ITerminalIdGetter[] getters = new ITerminalIdGetter[0];

	private ITerminalIdSetter[] setters = new ITerminalIdSetter[0];

	private TerminalIdAccess() {}

	@Override
	public Optional<Long> getIdFrom(EntityPlayer player) {
		for (ITerminalIdGetter getter : getters) {
			final Optional<Long> result = getter.getFor(player);
			if (result.isPresent()) return result;
		}

		return Optional.absent();
	}

	@Override
	public void register(ITerminalIdGetter getter) {
		Preconditions.checkNotNull(getter);
		getters = ArrayUtils.add(getters, getter);
		Arrays.sort(getters, new Comparator<ITerminalIdGetter>() {
			@Override
			public int compare(ITerminalIdGetter o1, ITerminalIdGetter o2) {
				return Ints.compare(o1.getPriority(), o2.getPriority());
			}
		});
	}

	@Override
	public boolean setIdFor(EntityPlayer player, long guid) {
		for (ITerminalIdSetter setter : setters)
			if (setter.setFor(player, guid)) return true;

		return false;
	}

	@Override
	public void register(ITerminalIdSetter setter) {
		Preconditions.checkNotNull(setter);
		setters = ArrayUtils.add(setters, setter);

		Arrays.sort(setters, new Comparator<ITerminalIdSetter>() {
			@Override
			public int compare(ITerminalIdSetter o1, ITerminalIdSetter o2) {
				return Ints.compare(o1.getPriority(), o2.getPriority());
			}
		});
	}

} @Doc({ "This peripheral is used to control terminal glasses and wireless keyboard.",
		"There is one global surface and one private surface for every glasses user.",
		"All calls names .add*() will return object that can be later used to modify it.",
		"To make changes visible to players, call .sync().",
		"This peripheral signals few events. Full list available here: http://goo.gl/8Hf2yA",
		"Simple demo: http://goo.gl/n5HPN8" })
@PeripheralTypeId("openperipheral_bridge")
@FeatureGroup("openperipheral-glasses")
public class TileEntityGlassesBridge extends OpenTileEntity implements IAttachable, IPlaceAwareTile, ICustomHarvestDrops, IClearable {

	private static final String GLOBAL_FAKE_PLAYER_NAME = "$GLOBAL$";

	public static final String TAG_GUID = "guid";

	private static final String EVENT_PLAYER_ATTACH = "glasses_attach";

	private static final String EVENT_PLAYER_DETACH = "glasses_detach";

	private static class PlayerInfo {
		public final GameProfile profile;
		public final WeakReference<EntityPlayerMP> player;
		public final SurfaceServer surface;

		public PlayerInfo(long guid, EntityPlayerMP player) {
			this.player = new WeakReference<EntityPlayerMP>(player);
			this.profile = player.getGameProfile();
			this.surface = SurfaceServer.createPrivateSurface(guid);
		}
	}

	private final Map<UUID, PlayerInfo> knownPlayersByUUID = Maps.newHashMap();
	private final Map<String, PlayerInfo> knownPlayersByName = Maps.newHashMap();
	private List<Object> globalFullDataPackets;

	private Set<IArchitectureAccess> computers = Sets.newIdentityHashSet();

	private Optional<Long> guid = Optional.absent();

	private SurfaceServer globalSurface;

	@IncludeInterface(IContainer.class)
	private IContainer<Drawable> getDrawablesContainer() {
		return globalSurface.drawablesContainer;
	}

	@IncludeInterface(IDrawableFactory.class)
	private IDrawableFactory getDrawablesFactory() {
		return globalSurface.drawablesFactory;
	}

	public TileEntityGlassesBridge() {}

	private void rebuildPlayerNamesMap() {
		knownPlayersByName.clear();

		for (PlayerInfo info : knownPlayersByUUID.values()) {
			final EntityPlayerMP player = info.player.get();
			if (isPlayerValid(player)) {
				String name = player.getGameProfile().getName();
				knownPlayersByName.put(name, info);
			}
		}
	}

	public long getOrCreateGuid() {
		if (this.guid.isPresent())
			return this.guid.get();

		final long newGuid = TerminalUtils.generateGuid();
		this.guid = Optional.of(newGuid);
		return newGuid;
	}

	public void registerTerminal(EntityPlayerMP player) {
		if (!knownPlayersByUUID.containsKey(player.getGameProfile().getId())) {
			final PlayerInfo playerInfo = new PlayerInfo(getOrCreateGuid(), player);
			final GameProfile gameProfile = player.getGameProfile();

			knownPlayersByUUID.put(gameProfile.getId(), playerInfo);
			rebuildPlayerNamesMap();
			queueEvent(EVENT_PLAYER_ATTACH, player);

			sentStoredFullDataToPlayer(player);
		}
	}

	private void sentStoredFullDataToPlayer(EntityPlayer player) {
		if (globalFullDataPackets != null) NetworkEventManager.INSTANCE.dispatcher().senders.player.sendMessages(globalFullDataPackets, player);
	}

	private static interface IEventArgsSource {
		public Object[] getArgs(IArchitectureAccess access);
	}

	private void queueEvent(String event, EntityPlayer user, IEventArgsSource source) {
		final GameProfile gameProfile = user.getGameProfile();
		final UUID userId = gameProfile.getId();
		final String idString = userId != null? userId.toString() : null;
		final String userName = gameProfile.getName();

		for (IArchitectureAccess computer : computers) {
			final Object[] extra = source.getArgs(computer);
			final Object[] args = new Object[3 + extra.length];
			System.arraycopy(extra, 0, args, 3, extra.length);
			args[0] = computer.peripheralName();
			args[1] = userName;
			args[2] = idString;

			computer.signal(event, args);
		}
	}

	private void queueEvent(String event, EntityPlayer user, final Object... args) {
		queueEvent(event, user, new IEventArgsSource() {
			@Override
			public Object[] getArgs(IArchitectureAccess access) {
				return args;
			}
		});
	}

	public void onChatCommand(String event, String content, EntityPlayer player) {
		queueEvent(event, player, content);
	}

	@Override
	public void updateEntity() {
		super.updateEntity();
		if (worldObj.isRemote) return;

		final long guid = getOrCreateGuid();

		if (globalSurface == null) globalSurface = SurfaceServer.createPublicSurface(guid);
		TerminalManagerServer.instance.registerBridge(guid, this);

		boolean playersRemoved = false;
		Iterator<PlayerInfo> it = knownPlayersByUUID.values().iterator();
		while (it.hasNext()) {
			final PlayerInfo info = it.next();
			final EntityPlayerMP player = info.player.get();

			if (!isPlayerValid(player)) {
				queueEvent(EVENT_PLAYER_DETACH, player);
				sendClearPacketToPlayer(player, globalSurface);
				sendClearPacketToPlayer(player, info.surface);
				it.remove();
				playersRemoved = true;
			}
		}

		if (playersRemoved) rebuildPlayerNamesMap();
	}

	private static void sendClearPacketToPlayer(EntityPlayerMP player, SurfaceServer surface) {
		surface.drawablesContainer.createClearPacket().sendToPlayer(player);
	}

	private boolean isPlayerValid(EntityPlayerMP player) {
		if (player == null) return false;

		if (player.isDead && !isPlayerLogged(player)) return false;

		final Optional<Long> guid = TerminalIdAccess.instance.getIdFrom(player);
		return guid.isPresent() && guid.get() == getOrCreateGuid();
	}

	private static boolean isPlayerLogged(EntityPlayerMP player) {
		final GameProfile gameProfile = player.getGameProfile();
		@SuppressWarnings("unchecked")
		List<EntityPlayerMP> players = MinecraftServer.getServer().getConfigurationManager().playerEntityList;
		for (EntityPlayerMP p : players) {
			if (p.getGameProfile().equals(gameProfile)) return true;
		}

		return false;
	}

	@Override
	public void writeToNBT(NBTTagCompound tag) {
		super.writeToNBT(tag);
		if (guid.isPresent()) tag.setLong(TAG_GUID, guid.get());
	}

	@Override
	public void readFromNBT(NBTTagCompound tag) {
		super.readFromNBT(tag);
		this.guid = Optional.fromNullable(TerminalUtils.extractGuid(tag));
	}

	@Override
	public void onBlockPlacedBy(EntityPlayer player, ForgeDirection side, ItemStack stack, float hitX, float hitY, float hitZ) {
		NBTTagCompound tag = stack.getTagCompound();
		if (tag != null && tag.hasKey(TAG_GUID)) this.guid = Optional.of(tag.getLong(TAG_GUID));
	}

	@Override
	public void addHarvestDrops(EntityPlayer player, List<ItemStack> drops) {
		ItemStack result = new ItemStack(getBlockType());
		if (guid.isPresent()) {
			NBTTagCompound tag = ItemUtils.getItemTag(result);
			tag.setLong(TAG_GUID, guid.get());
		}
		drops.add(result);
	}

	@Override
	public boolean suppressNormalHarvestDrops() {
		return true;
	}

	@Override
	public void addComputer(IArchitectureAccess computer) {
		if (!computers.contains(computer)) {
			computers.add(computer);
		}
	}

	@Override
	public void removeComputer(IArchitectureAccess computer) {
		computers.remove(computer);
	}

	public void handleUserEvent(final GlassesClientEvent evt) {
		queueEvent(evt.getEventName(), evt.sender, new IEventArgsSource() {
			@Override
			public Object[] getArgs(IArchitectureAccess access) {
				return evt.getEventArgs(access);
			}
		});
	}

	public SurfaceServer getSurface(String username) {
		if (GLOBAL_FAKE_PLAYER_NAME.equals(username)) return globalSurface;
		PlayerInfo info = knownPlayersByName.get(username);
		return info != null? info.surface : null;
	}

	public SurfaceServer getSurface(UUID uuid) {
		if (TerminalUtils.GLOBAL_SURFACE_UUID.equals(uuid)) return globalSurface;
		PlayerInfo info = knownPlayersByUUID.get(uuid);
		return info != null? info.surface : null;
	}

	// never, ever make this asynchronous
	@ScriptCallable(description = "Send updates to client. Without it changes won't be visible", name = "sync")
	public void syncContents() {
		final DrawableContainerMaster drawables = globalSurface.drawablesContainer;
		synchronized (drawables) {
			final boolean globalChanged = drawables.hasUpdates();

			if (globalChanged || globalFullDataPackets == null) {
				final TerminalEvent.Data globalFullData = drawables.createFullDataEvent();
				globalFullDataPackets = globalFullData.serialize();
			}

			List<Object> globalUpdateDataPackets = null;

			if (globalChanged) {
				final TerminalEvent.Data globalDelta = drawables.createUpdateDataEvent();
				globalUpdateDataPackets = globalDelta.serialize();
			}

			final ITargetedPacketSender<EntityPlayer> playerSender = NetworkEventManager.INSTANCE.dispatcher().senders.player;

			for (PlayerInfo info : knownPlayersByUUID.values()) {
				final EntityPlayerMP player = info.player.get();
				if (isPlayerValid(player)) {
					if (globalUpdateDataPackets != null) playerSender.sendMessages(globalUpdateDataPackets, player);
					sendPrivateUpdateToPlayer(player, info);
				}
			}
		}
	}

	private static void sendPrivateUpdateToPlayer(EntityPlayerMP player, PlayerInfo info) {
		final SurfaceServer privateSurface = info.surface;
		final DrawableContainerMaster drawables = privateSurface.drawablesContainer;
		synchronized (drawables) {
			if (drawables.hasUpdates()) {
				final TerminalEvent.Data privateUpdateDataPackets = drawables.createUpdateDataEvent();
				privateUpdateDataPackets.sendToPlayer(player);
			}
		}
	}

	private static void sendFullDataPacketToPlayer(EntityPlayer player, final SurfaceServer surface) {
		surface.drawablesContainer.createFullDataEvent().sendToPlayer(player);
	}

	private void sendPrivateFullDataToPlayer(EntityPlayer player) {
		UUID playerUuid = player.getGameProfile().getId();
		PlayerInfo info = knownPlayersByUUID.get(playerUuid);
		if (info != null) sendFullDataPacketToPlayer(player, info.surface);
	}

	public void handlePrivateDrawableResetRequest(TerminalEvent.PrivateDrawableReset evt) {
		sendPrivateFullDataToPlayer(evt.sender);
	}

	public void handlePublicDrawableResetRequest(TerminalEvent.PublicDrawableReset evt) {
		sentStoredFullDataToPlayer(evt.sender);
	}

	@Asynchronous
	@ScriptCallable(returnTypes = ReturnType.TABLE, description = "Get the names of all the users linked up to this bridge")
	public List<GameProfile> getUsers() {
		List<GameProfile> result = Lists.newArrayList();
		for (PlayerInfo info : knownPlayersByUUID.values())
			result.add(info.profile);

		return result;
	}

	@Asynchronous
	@ScriptCallable(returnTypes = ReturnType.STRING, name = "getGuid", description = "Get the Guid of this bridge")
	public String getGuidString() {
		return TerminalUtils.formatTerminalId(getOrCreateGuid());
	}

	@Override
	@IncludeOverride
	public void clear() {
		globalSurface.drawablesContainer.clear();

		for (PlayerInfo info : knownPlayersByUUID.values())
			info.surface.drawablesContainer.clear();
	}

	@Asynchronous
	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the surface of a user to draw privately on their screen")
	public SurfaceServer getSurfaceByName(@Arg(name = "username", description = "The username of the user to get the draw surface for") String username) {
		SurfaceServer playerSurface = getSurface(username);
		Preconditions.checkNotNull(playerSurface, "Invalid player");
		return playerSurface;
	}

	@Asynchronous
	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the surface of a user to draw privately on their screen")
	public SurfaceServer getSurfaceByUUID(@Arg(name = "uuid", description = "The uuid of the user to get the draw surface for") UUID uuid) {
		SurfaceServer playerSurface = getSurface(uuid);
		Preconditions.checkNotNull(playerSurface, "Invalid player");
		return playerSurface;
	}

	@Asynchronous
	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Returns object used for controlling player capture mode")
	public GuiCaptureControl getCaptureControl(@Arg(name = "uuid") UUID uuid) {
		PlayerInfo info = knownPlayersByUUID.get(uuid);
		return info != null? new GuiCaptureControl(getOrCreateGuid(), info.player) : null;
	}
} public class TerminalManagerServer {
	private TerminalManagerServer() {}

	public static final TerminalManagerServer instance = new TerminalManagerServer();

	private final Map<Long, TileEntityGlassesBridge> listeners = new MapMaker().weakValues().makeMap();

	private static final String EVENT_CHAT_COMMAND = "glasses_chat_command";

	private static final String EVENT_CHAT_MESSAGE = "glasses_chat_message";

	public class ForgeListener {

		@SubscribeEvent(priority = EventPriority.HIGH)
		public void onServerChatEvent(ServerChatEvent event) {
			final EntityPlayerMP player = event.player;
			final Optional<Long> guid = TerminalIdAccess.instance.getIdFrom(player);
			if (guid.isPresent()) {
				if (event.message.startsWith("$$")) {
					sendChatEvent(EVENT_CHAT_COMMAND, player, guid.get(), event.message.substring(2).trim());
					event.setCanceled(true);
				} else if (Config.listenToAllChat) {
					sendChatEvent(EVENT_CHAT_MESSAGE, player, guid.get(), event.message);
				}

			}
		}

		private void sendChatEvent(String event, EntityPlayerMP player, Long guid, String message) {
			final TileEntityGlassesBridge listener = listeners.get(guid);
			if (listener != null) listener.onChatCommand(event, message, player);
		}

		@SubscribeEvent
		public void onResetRequest(TerminalEvent.PrivateDrawableReset evt) {
			TileEntityGlassesBridge listener = listeners.get(evt.terminalId);
			if (listener != null) listener.handlePrivateDrawableResetRequest(evt);
		}

		@SubscribeEvent
		public void onResetRequest(TerminalEvent.PublicDrawableReset evt) {
			TileEntityGlassesBridge listener = listeners.get(evt.terminalId);
			if (listener != null) listener.handlePublicDrawableResetRequest(evt);
		}

		@SubscribeEvent
		public void onTerminalRegister(TerminalRegisterEvent evt) {
			TileEntityGlassesBridge listener = listeners.get(evt.terminalId);
			if (listener != null) listener.registerTerminal(evt.player);
		}

		@SubscribeEvent
		public void onGlassesEvent(GlassesClientEvent evt) {
			TileEntityGlassesBridge listener = listeners.get(evt.guid);
			if (listener != null) listener.handleUserEvent(evt);
		}
	}

	public Object createForgeListener() {
		return new ForgeListener();
	}

	public class FmlListener {
		@SubscribeEvent
		public void onPlayerTick(PlayerTickEvent evt) {
			if (evt.phase == Phase.START && evt.player instanceof EntityPlayerMP) {
				final Optional<Long> guid = TerminalIdAccess.instance.getIdFrom(evt.player);
				if (guid.isPresent()) {
					TileEntityGlassesBridge listener = listeners.get(guid.get());
					if (listener != null) listener.registerTerminal((EntityPlayerMP)evt.player);
				}
			}
		}
	}

	public Object createFmlListener() {
		return new FmlListener();
	}

	public void registerBridge(long terminalId, TileEntityGlassesBridge bridge) {
		listeners.put(terminalId, bridge);
	}
} public class BlockPIM extends BlockOP {

	public static class Icons {
		public static IIcon black;
		public static IIcon blue;
	}

	public BlockPIM() {
		super(Material.ground);
		setStepSound(soundTypeMetal);
		setBlockBounds(0f, 0f, 0f, 1f, 0.3f, 1f);
		setRenderMode(RenderMode.BLOCK_ONLY);
	}

	@Override
	@SideOnly(Side.CLIENT)
	public void registerBlockIcons(IIconRegister registry) {
		super.registerBlockIcons(registry);

		Icons.black = registry.registerIcon("openperipheraladdons:pim_black");
		Icons.blue = registry.registerIcon("openperipheraladdons:pim_blue");
	}

	@Override
	public boolean isOpaqueCube() {
		return false;
	}

	@Override
	public boolean isSideSolid(IBlockAccess world, int x, int y, int z, ForgeDirection side) {
		return side == ForgeDirection.DOWN;
	}

	@Override
	public void onEntityCollidedWithBlock(World world, int x, int y, int z, Entity entity) {
		world.markBlockForUpdate(x, y, z);
		if (!world.isRemote && entity instanceof EntityPlayer) {
			TileEntityPIM pi = getTileEntity(world, x, y, z, TileEntityPIM.class);
			if (pi != null) pi.trySetPlayer((EntityPlayer)entity);
		}
	}

	@Override
	public boolean shouldDropFromTeAfterBreak() {
		return false;
	}

} public class TileEntityPIM extends OpenTileEntity implements IInventory, IAttachable {

	private WeakReference<EntityPlayer> player;

	private Set<IArchitectureAccess> computers = Sets.newIdentityHashSet();

	public EntityPlayer getPlayer() {
		return player != null? player.get() : null;
	}

	@Override
	public int getSizeInventory() {
		EntityPlayer player = getPlayer();
		return player != null? player.inventory.getSizeInventory() : 0;
	}

	@Override
	public ItemStack getStackInSlot(int i) {
		EntityPlayer player = getPlayer();
		return player != null? player.inventory.getStackInSlot(i) : null;
	}

	@Override
	public ItemStack decrStackSize(int i, int j) {
		EntityPlayer player = getPlayer();
		return player != null? player.inventory.decrStackSize(i, j) : null;
	}

	@Override
	public ItemStack getStackInSlotOnClosing(int i) {
		return null;
	}

	@Override
	public void setInventorySlotContents(int i, ItemStack itemstack) {
		EntityPlayer player = getPlayer();
		if (player != null) player.inventory.setInventorySlotContents(i, itemstack);
	}

	@Override
	public String getInventoryName() {
		EntityPlayer player = getPlayer();
		return player != null? player.getCommandSenderName() : "pim";
	}

	@Override
	public boolean hasCustomInventoryName() {
		return false;
	}

	@Override
	public int getInventoryStackLimit() {
		EntityPlayer player = getPlayer();
		return player != null? player.inventory.getInventoryStackLimit() : 0;
	}

	@Override
	public boolean isUseableByPlayer(EntityPlayer entityplayer) {
		return true;
	}

	@Override
	public void openInventory() {}

	@Override
	public void closeInventory() {}

	@Override
	public boolean isItemValidForSlot(int i, ItemStack itemstack) {
		return getPlayer() != null;
	}

	@Override
	public void addComputer(IArchitectureAccess computer) {
		synchronized (computers) {
			computers.add(computer);
		}
	}

	@Override
	public synchronized void removeComputer(IArchitectureAccess computer) {
		synchronized (computers) {
			computers.remove(computer);
		}
	}

	public boolean hasPlayer() {
		if (worldObj == null) return false;
		return worldObj.getBlockMetadata(xCoord, yCoord, zCoord) == 1;
	}

	private void setPlayer(EntityPlayer newPlayer) {
		worldObj.playSoundEffect(xCoord + 0.5D, yCoord + 0.1D, zCoord + 0.5D, "random.click", 0.3F, 0.6F);
		worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, newPlayer == null? 0 : 1, 3);

		if (player != null) {
			EntityPlayer prevPlayer = player.get();
			if (prevPlayer != null) {
				GameProfile profile = prevPlayer.getGameProfile();
				final UUID uuid = profile.getId();
				fireEvent("player_off", profile.getName(), uuid != null? uuid.toString() : "?");
			}
		}

		player = null;

		if (newPlayer != null) {
			player = new WeakReference<EntityPlayer>(newPlayer);
			GameProfile profile = newPlayer.getGameProfile();
			final UUID uuid = profile.getId();
			fireEvent("player_on", profile.getName(), uuid != null? uuid.toString() : "?");
		}
	}

	public void trySetPlayer(EntityPlayer newPlayer) {
		if (newPlayer == null) return;
		EntityPlayer current = getPlayer();
		if (current == null && isPlayerValid(newPlayer)) setPlayer(newPlayer);
	}

	private void fireEvent(String eventName, Object... args) {
		synchronized (computers) {
			for (IArchitectureAccess computer : computers) {
				Object[] extendedArgs = ArrayUtils.add(args, computer.peripheralName());
				computer.signal(eventName, extendedArgs);
			}
		}
	}

	private boolean isPlayerValid(EntityPlayer player) {
		if (player == null) return false;
		int playerX = MathHelper.floor_double(player.posX);
		int playerY = MathHelper.floor_double(player.posY + 0.5D);
		int playerZ = MathHelper.floor_double(player.posZ);
		return playerX == xCoord && playerY == yCoord && playerZ == zCoord;
	}

	/**
	 * TODO: fix this. This doesnt seem.. efficient.
	 */
	@Override
	public void updateEntity() {
		if (!worldObj.isRemote) {
			EntityPlayer player = getPlayer();
			if (player != null && !isPlayerValid(player)) setPlayer(null);
		}
	}
} public class BlockSelector extends BlockOP implements ISelectionAware {
	private AxisAlignedBB selectorAABB;

	public BlockSelector() {
		super(Material.ground);
		setRotationMode(BlockRotationMode.TWELVE_DIRECTIONS);
		setPlacementMode(BlockPlacementMode.ENTITY_ANGLE);
		setRenderMode(RenderMode.BOTH);
		setInventoryRenderOrientation(Orientation.XP_ZP);
	}

	@Override
	@SideOnly(Side.CLIENT)
	public void registerBlockIcons(IIconRegister registry) {
		super.registerBlockIcons(registry);

		setTexture(ForgeDirection.UP, registry.registerIcon("openperipheraladdons:selector_front"));
		setTexture(ForgeDirection.DOWN, registry.registerIcon("openperipheraladdons:selector_bottom"));
	}

	@Override
	public AxisAlignedBB getSelectedBoundingBoxFromPool(World world, int x, int y, int z) {
		return selectorAABB != null? selectorAABB : super.getSelectedBoundingBoxFromPool(world, x, y, z);
	}

	@Override
	public boolean isOpaqueCube() {
		return false;
	}

	@Override
	public boolean onSelected(World world, int x, int y, int z, DrawBlockHighlightEvent evt) {
		final TileEntitySelector selector = getTileEntity(world, x, y, z, TileEntitySelector.class);

		if (selector != null) {
			final MovingObjectPosition mop = evt.target;
			this.selectorAABB = selector.getSelection(mop.hitVec, mop.sideHit);
		} else this.selectorAABB = null;

		return false;
	}
} @PeripheralTypeId("openperipheral_selector")
@FeatureGroup("openperipheral-selector")
public class TileEntitySelector extends SyncedTileEntity implements IActivateAwareTile, IAttachable, IHasGui {

	public static class ItemSlot {
		public final int slot;
		public final double x;
		public final double y;
		public final AxisAlignedBB box;
		public final double size;

		public ItemSlot(int absSlot, double size, double x, double y) {
			this.slot = absSlot;
			this.size = size;
			this.x = x;
			this.y = y;
			this.box = AxisAlignedBB.getBoundingBox(x - size, 1 - size, y - size, x + size, 1 + size, y + size);
		}
	}

	private static final List<ItemSlot> BOXES_0 = ImmutableList.of();

	private static final List<ItemSlot> BOXES_1 = ImmutableList.of(new ItemSlot(0, 0.2, 0.5, 0.5));

	private static final double PADDING_2 = 0.05;

	private static final double SIZE_2 = 0.12;

	private static final double DELTA_2 = SIZE_2 + PADDING_2;

	private static final ImmutableList<ItemSlot> BOXES_2 = ImmutableList.of(
			new ItemSlot(3, SIZE_2, 0.5 - DELTA_2, 0.5 + DELTA_2),
			new ItemSlot(4, SIZE_2, 0.5 + DELTA_2, 0.5 + DELTA_2),
			new ItemSlot(0, SIZE_2, 0.5 - DELTA_2, 0.5 - DELTA_2),
			new ItemSlot(1, SIZE_2, 0.5 + DELTA_2, 0.5 - DELTA_2));

	private static final double SIZE_3 = 0.085;

	private static final double PADDING_3 = 0.08;

	private static final double DELTA_3 = SIZE_3 + PADDING_3 + SIZE_3;

	private static final List<ItemSlot> BOXES_3 = ImmutableList.of(
			new ItemSlot(6, SIZE_3, 0.5 - DELTA_3, 0.5 + DELTA_3),
			new ItemSlot(7, SIZE_3, 0.5, 0.5 + DELTA_3),
			new ItemSlot(8, SIZE_3, 0.5 + DELTA_3, 0.5 + DELTA_3),
			new ItemSlot(3, SIZE_3, 0.5 - DELTA_3, 0.5),
			new ItemSlot(4, SIZE_3, 0.5, 0.5),
			new ItemSlot(5, SIZE_3, 0.5 + DELTA_3, 0.5),
			new ItemSlot(0, SIZE_3, 0.5 - DELTA_3, 0.5 - DELTA_3),
			new ItemSlot(1, SIZE_3, 0.5, 0.5 - DELTA_3),
			new ItemSlot(2, SIZE_3, 0.5 + DELTA_3, 0.5 - DELTA_3));

	private Set<IArchitectureAccess> computers = Sets.newIdentityHashSet();

	private final SyncableItemStack[] slots = new SyncableItemStack[9];

	private SoftReference<EntityItem> displayEntity = new SoftReference<EntityItem>(null);

	private int gridSize;

	public TileEntitySelector() {
		for (int i = 0; i < 9; i++) {
			SyncableItemStack item = new SyncableItemStack();
			syncMap.put("Stack" + i, item);
			slots[i] = item;
		}

		final ISyncListener listener = new ISyncListener() {
			@Override
			public void onSync(Set<ISyncableObject> changes) {
				gridSize = calculateGridSize();
			}
		};
		syncMap.addUpdateListener(listener);
		syncMap.addSyncListener(listener);
	}

	@Override
	protected void createSyncedFields() {}

	private boolean hasStack(int slot) {
		return slots[slot].get() != null;
	}

	// We're having a dynamic display size, i.e. if there's only an item in the
	// first slot, only the single item will be shown - slightly enlarged. If
	// there are items one slot further out, we're showing a 2x2 grid and so on.
	// There's probably a mathematical way to do this more efficiently, but meh.
	public int calculateGridSize() {
		if (hasStack(2) || hasStack(5) || hasStack(6) || hasStack(7) || hasStack(8)) return 3;
		if (hasStack(1) || hasStack(3) || hasStack(4)) return 2;
		if (hasStack(0)) return 1;

		return 0;
	}

	public int getGridSize() {
		return gridSize;
	}

	@Override
	public boolean onBlockActivated(EntityPlayer player, int side, float hitX, float hitY, float hitZ) {
		if (worldObj.isRemote) return true;
		if (player.isSneaking()) return true;

		Vec3 vec = Vec3.createVectorHelper(xCoord + hitX, yCoord + hitY, zCoord + hitZ);
		ItemSlot slot = getClickedSlot(vec, side);

		if (slot == null) {
			openGui(OpenPeripheralAddons.instance, player);
		} else {
			if (hasStack(slot.slot)) signalSlotClick(slot.slot);
		}

		return true;
	}

	public ItemStack getSlot(int slot) {
		return slots[slot].get();
	}

	@Asynchronous
	@ScriptCallable(description = "Get the item currently being displayed in a specific slot", returnTypes = ReturnType.TABLE, name = "getSlot")
	public ItemStack getSlotOneBased(@Arg(name = "slot", description = "The slot you want to get details about") Index slot) {

		slot.checkElementIndex("slot id", 10);
		return slots[slot.value].get();
	}

	@Asynchronous
	@ScriptCallable(description = "Gets all slots", returnTypes = ReturnType.TABLE)
	public List<ItemStack> getSlots() {
		List<ItemStack> result = Lists.newArrayList();

		for (SyncableItemStack slot : slots)
			result.add(slot.get());

		return result;
	}

	@ScriptCallable(description = "Set the items being displayed in all slots")
	public void setSlots(
			@Env(Constants.ARG_ARCHITECTURE) IArchitecture access,
			@Arg(name = "items", description = "A table containing itemstacks") Map<Index, ItemStack> stacks) {
		for (int slot = 0; slot < 9; slot++) {
			final ItemStack value = stacks.get(access.wrapObject(slot));
			if (value != null) value.stackSize = 1;

			this.slots[slot].set(value);
		}

		sync();
	}

	@ScriptCallable(description = "Set the item being displayed on a specific slot")
	public void setSlot(
			@Arg(name = "slot", description = "The slot you want to modify") Index slot,
			@Optionals @Arg(name = "item", description = "The item you want to display. nil to set empty") ItemStack stack) {

		slot.checkElementIndex("slot id", 10);

		if (stack != null) stack.stackSize = 1;

		this.slots[slot.value].set(stack);
		sync();
	}

	private void signalSlotClick(int slot) {
		for (IArchitectureAccess computer : computers) {
			final Object[] args = new Object[] { computer.createIndex(slot), computer.peripheralName() };
			computer.signal("slot_click", args);
		}
	}

	@Override
	public void addComputer(IArchitectureAccess computer) {
		computers.add(computer);
	}

	@Override
	public void removeComputer(IArchitectureAccess computer) {
		computers.remove(computer);
	}

	@Override
	public boolean canOpenGui(EntityPlayer player) {
		// Don't use default GUI mechanism, since this block has special rules
		return false;
	}

	@Override
	public Object getClientGui(EntityPlayer player) {
		return new GuiSelector(new ContainerSelector(player.inventory, this));
	}

	@Override
	public Object getServerGui(EntityPlayer player) {
		return new ContainerSelector(player.inventory, this);
	}

	public IInventory createInventoryWrapper() {
		return new IInventory() {

			@Override
			public void setInventorySlotContents(int slot, ItemStack stack) {
				slots[slot].set(stack);
			}

			@Override
			public void openInventory() {}

			@Override
			public void markDirty() {
				if (!worldObj.isRemote) sync();
			}

			@Override
			public boolean isUseableByPlayer(EntityPlayer player) {
				return (getWorldObj().getTileEntity(xCoord, yCoord, zCoord) == TileEntitySelector.this)
						&& (player.getDistanceSq(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5) <= 64.0D);
			}

			@Override
			public boolean isItemValidForSlot(int slot, ItemStack stack) {
				return true;
			}

			@Override
			public boolean hasCustomInventoryName() {
				return false;
			}

			@Override
			public ItemStack getStackInSlotOnClosing(int slot) {
				return null;
			}

			@Override
			public ItemStack getStackInSlot(int slot) {
				return slots[slot].get();
			}

			@Override
			public int getSizeInventory() {
				return 9;
			}

			@Override
			public int getInventoryStackLimit() {
				return 1;
			}

			@Override
			public String getInventoryName() {
				return "selector";
			}

			@Override
			public ItemStack decrStackSize(int p_70298_1_, int p_70298_2_) {
				throw new UnsupportedOperationException();
			}

			@Override
			public void closeInventory() {}
		};
	}

	public AxisAlignedBB getSelection(Vec3 hitVec, int side) {
		final Orientation orientation = getOrientation();
		final Vec3 mappedVec = BlockSpaceTransform.instance.mapWorldToBlock(orientation, hitVec.xCoord - xCoord, hitVec.yCoord - yCoord, hitVec.zCoord - zCoord);

		final int gridSize = getGridSize();

		for (ItemSlot center : getSlots(gridSize)) {
			final AxisAlignedBB aabb = center.box;
			if (aabb.isVecInside(mappedVec)) return BlockSpaceTransform.instance.mapBlockToWorld(orientation, aabb).offset(xCoord, yCoord, zCoord);
		}

		return null;
	}

	private ItemSlot getClickedSlot(Vec3 hitVec, int side) {
		final Orientation orientation = getOrientation();
		final Vec3 mappedVec = BlockSpaceTransform.instance.mapWorldToBlock(orientation, hitVec.xCoord - xCoord, hitVec.yCoord - yCoord, hitVec.zCoord - zCoord);

		int gridSize = getGridSize();
		for (ItemSlot center : getSlots(gridSize)) {
			if (center.box.isVecInside(mappedVec)) return center;
		}

		return null;
	}

	public List<ItemSlot> getSlots(int gridSize) {
		switch (gridSize) {
			case 1:
				return BOXES_1;
			case 2:
				return BOXES_2;
			case 3:
				return BOXES_3;
			default:
				return BOXES_0;
		}
	}

	public EntityItem getDisplayEntity() {
		EntityItem result = displayEntity.get();
		if (result == null) {
			result = new EntityItem(getWorldObj(), 0, 0, 0);
			result.hoverStart = 0.0F;

			displayEntity = new SoftReference<EntityItem>(result);
		}

		return result;
	}

	@Override
	public void readFromNBT(NBTTagCompound tag) {
		super.readFromNBT(tag);
		gridSize = calculateGridSize();
	}

} @FeatureGroup("openperipheral-sensor")
public class AdapterSensor implements IPeripheralAdapter {

	private static final String DONT_EVER_CHANGE_THIS_TEXT_OTHERWISE_YOU_WILL_RUIN_EVERYTHING = "Entity not found";

	public static enum SupportedEntityTypes {
		MOB(EntityLiving.class),
		MINECART(EntityMinecart.class),
		ITEM(EntityItem.class),
		ITEM_FRAME(EntityItemFrame.class),
		PAINTING(EntityPainting.class);

		public final Class<? extends Entity> cls;

		private SupportedEntityTypes(Class<? extends Entity> cls) {
			this.cls = cls;
		}
	}

	// LRU cache with a maximum of 1000 entries; maps packed-RGB int -> color-bitmask int
	@SuppressWarnings("serial")
	private static final LinkedHashMap<Integer, Integer> COLOR_CACHE = new LinkedHashMap<Integer, Integer>(16, 0.75f, true) {
		@Override
		protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
			return size() > 1000;
		}
	};

	@Override
	public Class<?> getTargetClass() {
		return ISensorEnvironment.class;
	}

	@Override
	public String getSourceId() {
		return "openperipheral_sensor";
	}

	private static AxisAlignedBB getBoundingBox(Vec3 location, double range) {
		return AxisAlignedBB.getBoundingBox(
				location.xCoord, location.yCoord, location.zCoord,
				location.xCoord + 1, location.yCoord + 1, location.zCoord + 1)
				.expand(range, range, range);
	}

	private static AxisAlignedBB getBoundingBox(ISensorEnvironment env) {
		return getBoundingBox(env.getLocation(), env.getSensorRange());
	}

	private static List<Integer> listEntityIds(ISensorEnvironment env, Class<? extends Entity> entityClass) {
		List<Integer> ids = Lists.newArrayList();

		final AxisAlignedBB aabb = getBoundingBox(env);
		for (Entity entity : WorldUtils.getEntitiesWithinAABB(env.getWorld(), entityClass, aabb))
			ids.add(entity.getEntityId());

		return ids;
	}

	private static IMetaProviderProxy getEntityInfoById(ISensorEnvironment sensor, int mobId, Class<? extends Entity> cls) {
		Entity mob = sensor.getWorld().getEntityByID(mobId);
		Preconditions.checkArgument(cls.isInstance(mob), DONT_EVER_CHANGE_THIS_TEXT_OTHERWISE_YOU_WILL_RUIN_EVERYTHING);
		return getEntityInfo(sensor, mob);
	}

	private static IMetaProviderProxy getPlayerInfo(ISensorEnvironment sensor, String username) {
		EntityPlayer player = sensor.getWorld().getPlayerEntityByName(username);
		return getEntityInfo(sensor, player);
	}

	private static IMetaProviderProxy getPlayerInfo(ISensorEnvironment sensor, UUID uuid) {
		EntityPlayer player = sensor.getWorld().func_152378_a(uuid);
		return getEntityInfo(sensor, player);
	}

	private static IMetaProviderProxy getEntityInfo(ISensorEnvironment sensor, Entity mob) {
		Preconditions.checkNotNull(mob, DONT_EVER_CHANGE_THIS_TEXT_OTHERWISE_YOU_WILL_RUIN_EVERYTHING);
		final AxisAlignedBB aabb = getBoundingBox(sensor);

		Preconditions.checkArgument(mob.boundingBox.intersectsWith(aabb), DONT_EVER_CHANGE_THIS_TEXT_OTHERWISE_YOU_WILL_RUIN_EVERYTHING);
		final Vec3 sensorPos = sensor.getLocation();
		return OpcAccess.entityMetaBuilder.createProxy(mob, sensorPos);
	}

	private static boolean checkRange(int x, int y, int z, int rangeSq) {
		final int distSq = x * x + y * y + z * z;
		return (distSq == 0 || distSq > rangeSq);
	}

	private static String getBlockType(World world, Block block, int xPos, int yPos, int zPos) {
		if (block == null || world.isAirBlock(xPos, yPos, zPos)) return "AIR";
		else if (block.getMaterial().isLiquid()) return "LIQUID";
		else if (block.getMaterial().isSolid()) return "SOLID";
		else return "UNKNOWN";
	}

	private static int getBlockColorBitmask(World world, Block block, int xPos, int yPos, int zPos) {
		if (block == null) {
			return ColorUtils.ColorMeta.BLACK.bitmask; // Default black -- same as air.
		} else {
			int color = block.getMapColor(block.getDamageValue(world, xPos, yPos, zPos)).colorValue;
			Integer cached = COLOR_CACHE.get(color);
			if (cached != null) return cached;

			RGB rgb = new RGB(color);
			Integer nearestColorBitmask = ColorUtils.findNearestColor(rgb, 255).bitmask;
			COLOR_CACHE.put(color, nearestColorBitmask);
			return nearestColorBitmask;
		}
	}

	private static Map<String, Object> describeBlock(World world, int sx, int sy, int sz, int dx, int dy, int dz) {
		final int bx = sx + dx;
		final int by = sy + dy;
		final int bz = sz + dz;

		if (!world.blockExists(bx, by, bz)) return null;

		final Block block = world.getBlock(bx, by, bz);
		final String type = getBlockType(world, block, bx, by, bz);
		final int color = getBlockColorBitmask(world, block, bx, by, bz);

		Map<String, Object> tmp = Maps.newHashMap();
		tmp.put("x", dx);
		tmp.put("y", dy);
		tmp.put("z", dz);
		tmp.put("type", type);
		tmp.put("color", color);
		return tmp;
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the ids of all the mobs in range. Deprecated, please use getEntityIds('mob')")
	public List<Integer> getMobIds(ISensorEnvironment env) {
		return listEntityIds(env, EntityLiving.class);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get full details of a particular mob if it's in range. Deprecated, please use getEntityData(id, 'mob')")
	public IMetaProviderProxy getMobData(ISensorEnvironment sensor,
			@Arg(name = "mobId", description = "The id retrieved from getMobIds()") int id) {
		return getEntityInfoById(sensor, id, EntityLiving.class);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the ids of all the minecarts in range. Deprecated, please use getEntityIds('minecart')")
	public List<Integer> getMinecartIds(ISensorEnvironment env) {
		return listEntityIds(env, EntityMinecart.class);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get full details of a particular minecart if it's in range. Deprecated, please use getEntityIds(id, 'minecraft')")
	public IMetaProviderProxy getMinecartData(ISensorEnvironment sensor,
			@Arg(name = "minecartId", description = "The id retrieved from getMinecartIds()") int id) {
		return getEntityInfoById(sensor, id, EntityMinecart.class);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the ids of all entities of single type in range")
	public List<Integer> getEntityIds(ISensorEnvironment env, @Arg(name = "type") SupportedEntityTypes type) {
		return listEntityIds(env, type.cls);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get full details of a particular entity if it's in range")
	public IMetaProviderProxy getEntityData(ISensorEnvironment sensor,
			@Arg(name = "id", description = "The id retrieved from getEntityIds()") int id,
			@Arg(name = "type") SupportedEntityTypes type) {
		return getEntityInfoById(sensor, id, type.cls);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get the usernames of all the players in range")
	public List<GameProfile> getPlayers(ISensorEnvironment env) {
		List<EntityPlayer> players = WorldUtils.getEntitiesWithinAABB(env.getWorld(), EntityPlayer.class, getBoundingBox(env));

		List<GameProfile> names = Lists.newArrayList();
		for (EntityPlayer player : players)
			names.add(player.getGameProfile());

		return names;
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get full details of a particular player if they're in range")
	public IMetaProviderProxy getPlayerByName(ISensorEnvironment env,
			@Arg(name = "username", description = "The players username") String username) {
		return getPlayerInfo(env, username);
	}

	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get full details of a particular player if they're in range")
	public IMetaProviderProxy getPlayerByUUID(ISensorEnvironment env,
			@Arg(name = "uuid", description = "The players uuid") UUID uuid) {
		return getPlayerInfo(env, uuid);
	}

	@ScriptCallable(returnTypes = ReturnType.TABLE, description = "Get a table of information about the surrounding area. Includes map color and whether each block is UNKNOWN, AIR, LIQUID, or SOLID.")
	public List<Map<String, Object>> sonicScan(ISensorEnvironment env) {
		int range = 1 + env.getSensorRange() / 2;
		List<Map<String, Object>> results = Lists.newArrayList();
		Vec3 sensorPos = env.getLocation();
		int sx = MathHelper.floor_double(sensorPos.xCoord);
		int sy = MathHelper.floor_double(sensorPos.yCoord);
		int sz = MathHelper.floor_double(sensorPos.zCoord);

		final World world = env.getWorld();

		final int rangeSq = range * range;

		for (int dx = -range; dx <= range; dx++) {
			for (int dy = -range; dy <= range; dy++) {
				for (int dz = -range; dz <= range; dz++) {
					if (checkRange(dx, dy, dz, rangeSq)) {
						Map<String, Object> result = describeBlock(world, sx, sy, sz, dx, dy, dz);
						if (result != null) results.add(result);
					}
				}
			}
		}

		return results;
	}

	@ScriptCallable(returnTypes = ReturnType.TABLE, description = "Get a table of information about a single block in the surrounding area. Includes map color and whether each block is UNKNOWN, AIR, LIQUID, or SOLID.")
	public Map<String, Object> sonicScanTarget(ISensorEnvironment env,
			@Arg(name = "xOffset", description = "The target's offset from the sensor on the X-Axis.") int dx,
			@Arg(name = "yOffset", description = "The target's offset from the sensor on the Y-Axis.") int dy,
			@Arg(name = "zOffset", description = "The target's offset from the sensor on the Z-Axis.") int dz) {
		int range = 1 + env.getSensorRange() / 2;
		int rangeSq = range * range;
		if (checkRange(dx, dy, dz, rangeSq)) return null;

		Vec3 sensorPos = env.getLocation();
		int sx = MathHelper.floor_double(sensorPos.xCoord);
		int sy = MathHelper.floor_double(sensorPos.yCoord);
		int sz = MathHelper.floor_double(sensorPos.zCoord);

		return describeBlock(env.getWorld(), sx, sy, sz, dx, dy, dz);
	}

} public class BlockSensor extends BlockOP {

	public BlockSensor() {
		super(Material.iron);
		setHardness(0.5F);
		setRenderMode(RenderMode.TESR_ONLY);
	}

	@Override
	public boolean canCollideCheck(int par1, boolean par2) {
		return true;
	}

	@Override
	public boolean renderAsNormalBlock() {
		return false;
	}

	@Override
	public boolean isSideSolid(IBlockAccess world, int x, int y, int z, ForgeDirection side) {
		return side == ForgeDirection.DOWN;
	}

	@Override
	public boolean isOpaqueCube() {
		return false;
	}

	@Override
	public int getRenderType() {
		return OpenPeripheralAddons.renderId;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void getSubBlocks(Item item, CreativeTabs tab, List result) {
		super.getSubBlocks(item, tab, result);
		if (Config.addTurtlesToCreative && Loader.isModLoaded(Mods.COMPUTERCRAFT)) ModuleComputerCraft.listSensorTurtles(result);
	}

} public class TileEntitySensor extends TileEntity implements ISensorEnvironment {

	private static final int ROTATION_SPEED = 3;

	private int rotation;

	public TileEntitySensor() {}

	public int getRotation() {
		return rotation;
	}

	@Override
	public void updateEntity() {
		rotation = (rotation + ROTATION_SPEED) % 360;
	}

	@Override
	public boolean isTurtle() {
		return false;
	}

	@Override
	public Vec3 getLocation() {
		return Vec3.createVectorHelper(xCoord, yCoord, zCoord);
	}

	@Override
	public World getWorld() {
		return worldObj;
	}

	@Override
	public int getSensorRange() {
		final World world = getWorld();
		return (world.isRaining() && world.isThundering())? Config.sensorRangeInStorm : Config.sensorRange;
	}

	@Override
	public boolean isValid() {
		return WorldUtils.isTileEntityValid(this);
	}

}
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import openmods.Mods;
import openmods.OpenMods;
import openmods.access.ApiFactory;
import openmods.access.ApiFactory.ApiProviderSetup;
import openmods.access.ApiProviderRegistry;
import openmods.api.IProxy;
import openmods.config.BlockInstances;
import openmods.config.ItemInstances;
import openmods.config.game.GameConfigProvider;
import openmods.config.game.ModStartupHelper;
import openmods.config.game.RegisterBlock;
import openmods.config.game.RegisterItem;
import openmods.config.properties.ConfigProcessing;
import openmods.network.event.NetworkEventManager;
import openperipheral.addons.api.ApiHolder;
import openperipheral.addons.api.IApiInterface;
import openperipheral.addons.glasses.BlockGlassesBridge;
import openperipheral.addons.glasses.GlassesEvent.GlassesChangeBackgroundEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesComponentMouseButtonEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesComponentMouseWheelEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesKeyDownEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesKeyUpEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesMouseButtonEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesMouseDragEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesMouseWheelEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetDragParamsEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetGuiVisibilityEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetKeyRepeatEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSignalCaptureEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesStopCaptureEvent;
import openperipheral.addons.glasses.ItemGlasses;
import openperipheral.addons.glasses.ItemGlassesBridge;
import openperipheral.addons.glasses.ItemKeyboard;
import openperipheral.addons.glasses.ItemTerminalMetaProvider;
import openperipheral.addons.glasses.NbtGuidProviders;
import openperipheral.addons.glasses.NbtTerminalMetaProvider;
import openperipheral.addons.glasses.TerminalEvent;
import openperipheral.addons.glasses.TerminalIdAccess;
import openperipheral.addons.glasses.TileEntityGlassesBridge;
import openperipheral.addons.glasses.server.TerminalManagerServer;
import openperipheral.addons.pim.BlockPIM;
import openperipheral.addons.pim.TileEntityPIM;
import openperipheral.addons.selector.BlockSelector;
import openperipheral.addons.selector.TileEntitySelector;
import openperipheral.addons.sensors.AdapterSensor;
import openperipheral.addons.sensors.BlockSensor;
import openperipheral.addons.sensors.TileEntitySensor;
import org.apache.commons.lang3.ObjectUtils;
package openperipheral.addons;


@Mod(modid = OpenPeripheralAddons.MODID, name = "OpenPeripheralAddons", version = "$VERSION$", dependencies = "required-after:OpenMods@[$LIB-VERSION$,$NEXT-LIB-VERSION$);required-after:OpenPeripheralApi@$OP-API-VERSION$;after:ComputerCraft@[1.70,]")
public class OpenPeripheralAddons {

	public static final String MODID = "OpenPeripheral";

	public static class Blocks implements BlockInstances {
		@RegisterBlock(name = "glassesbridge", tileEntity = TileEntityGlassesBridge.class, itemBlock = ItemGlassesBridge.class, textureName = "bridge")
		public static BlockGlassesBridge glassesBridge;

		@RegisterBlock(name = "pim", tileEntity = TileEntityPIM.class, unlocalizedName = "playerinventory", textureName = "pim_blue")
		public static BlockPIM pim;

		@RegisterBlock(name = "sensor", tileEntity = TileEntitySensor.class)