Input:
import mdteam.ait.AITMod;
import mdteam.ait.core.AITBlockEntityTypes;
import mdteam.ait.core.AITBlocks;
import mdteam.ait.core.blocks.ConsoleGeneratorBlock;
import mdteam.ait.core.blocks.types.HorizontalDirectionalBlock;
import mdteam.ait.core.item.SonicItem;
import mdteam.ait.registry.ConsoleRegistry;
import mdteam.ait.registry.ConsoleVariantRegistry;
import mdteam.ait.tardis.Tardis;
import mdteam.ait.tardis.TardisDesktop;
import mdteam.ait.tardis.console.ConsoleSchema;
import mdteam.ait.tardis.util.AbsoluteBlockPos;
import mdteam.ait.tardis.util.TardisUtil;
import mdteam.ait.tardis.variant.console.ConsoleVariantSchema;
import mdteam.ait.tardis.wrapper.client.manager.ClientTardisManager;
import mdteam.ait.tardis.wrapper.server.manager.ServerTardisManager;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.EndCrystalItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
import static mdteam.ait.core.blockentities.ConsoleBlockEntity.nextConsole;
import static mdteam.ait.core.blockentities.ConsoleBlockEntity.nextVariant;
import static mdteam.ait.tardis.util.TardisUtil.isClient;

Context:
src/main/java/mdteam/ait/core/blockentities/ConsoleGeneratorBlockEntity.java
package mdteam.ait.core.blockentities;

public class ConsoleGeneratorBlockEntity extends BlockEntity {

    private Identifier type;
    private Identifier variant;

    public ConsoleGeneratorBlockEntity(BlockPos pos, BlockState state) {
        super(AITBlockEntityTypes.CONSOLE_GENERATOR_ENTITY_TYPE, pos, state);
        this.type = ConsoleRegistry.HARTNELL.id();
    }

    public void useOn(World world, boolean sneaking, PlayerEntity player) {
        //if(world != TardisUtil.getTardisDimension()) return;
        if(player.getMainHandStack().getItem() instanceof SonicItem) {

            NbtCompound nbt = player.getMainHandStack().getOrCreateNbt();

            if(!nbt.contains("tardis")) return;

            ConsoleBlockEntity consoleBlockEntity = new ConsoleBlockEntity(pos,AITBlocks.CONSOLE.getDefaultState());

            consoleBlockEntity.setType(this.getConsoleSchema());
            consoleBlockEntity.setVariant(this.getConsoleVariant());

            this.getWorld().setBlockState(this.pos, AITBlocks.CONSOLE.getDefaultState());
            this.getWorld().addBlockEntity(consoleBlockEntity);

            world.playSound(null, this.pos, SoundEvents.BLOCK_BEACON_POWER_SELECT, SoundCategory.BLOCKS, 0.5f, 1.0f);

            /*if(!player.isCreative()) {
                player.getMainHandStack().decrement(1);
                // ItemEntity item = new ItemEntity(player.getWorld(), player.getX(), player.getY(), player.getZ(), new ItemStack(AITBlocks.CONSOLE_GENERATOR));
                // this.getWorld().spawnEntity(item);
            }*/
            return;
        }

        world.playSound(null, this.pos, SoundEvents.BLOCK_SCULK_CHARGE, SoundCategory.BLOCKS, 0.5f, 1.0f);

        if (sneaking) {
            this.changeConsole(nextVariant(this.getConsoleVariant()));
        } else
            this.changeConsole(nextConsole(this.getConsoleSchema()));
    }

    @Override
    public void writeNbt(NbtCompound nbt) {
        super.writeNbt(nbt);
        if(this.type != null)
            nbt.putString("console", this.type.toString());
        if (this.variant != null)
            nbt.putString("variant", this.variant.toString());
    }

    public ConsoleSchema getConsoleSchema() {
        if (type == null) {
            this.setConsoleSchema(ConsoleRegistry.HARTNELL.id());
        }

        return ConsoleRegistry.REGISTRY.get(type);
    }

    private void setConsoleSchema(Identifier type) {
        this.type = type;
        markDirty();
        if(this.getWorld() == null) return;
        this.getWorld().updateListeners(this.pos, this.getCachedState(), this.getCachedState(), Block.NOTIFY_LISTENERS);
    }

    public ConsoleVariantSchema getConsoleVariant() {

src/main/java/mdteam/ait/core/blocks/types/HorizontalDirectionalBlock.java
public class HorizontalDirectionalBlock extends Block {

    public static final DirectionProperty FACING = HorizontalFacingBlock.FACING;

    public HorizontalDirectionalBlock(Settings settings) {
        super(settings);

        this.setDefaultState(this.stateManager.getDefaultState().with(FACING, Direction.NORTH));
    }

    @Nullable
    @Override
    public BlockState getPlacementState(ItemPlacementContext ctx) {
        return this.getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite());
    }

    @Override
    protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
        builder.add(FACING);
    }
}

src/main/java/mdteam/ait/registry/ConsoleRegistry.java
public class ConsoleRegistry {
    public static final SimpleRegistry<ConsoleSchema> REGISTRY = FabricRegistryBuilder.createSimple(RegistryKey.<ConsoleSchema>ofRegistry(new Identifier(AITMod.MOD_ID, "console"))).buildAndRegister();
    public static ConsoleSchema register(ConsoleSchema schema) {
        return Registry.register(REGISTRY, schema.id(), schema);
    }

    //public static ConsoleSchema BOREALIS;
    public static ConsoleSchema CORAL; // @TODO implement the new coral when its made again
    public static ConsoleSchema HARTNELL;
    //public static ConsoleSchema TEMP; // @TODO implement the new hudolin when its made again

    public static void init() {
        HARTNELL = register(new HartnellConsole());
        //BOREALIS = register(new BorealisConsole());
        CORAL = register(new CoralConsole());
        //TEMP = register(new TempConsole());
    }
}

src/main/java/mdteam/ait/tardis/wrapper/server/manager/ServerTardisManager.java
public class ServerTardisManager extends TardisManager {

    public static final Identifier SEND = new Identifier("ait", "send_tardis");
    public static final Identifier UPDATE = new Identifier("ait", "update_tardis");
    private static final ServerTardisManager instance = new ServerTardisManager();
    // Changed from MultiMap to HashMap to fix some concurrent issues, maybe
    private final ConcurrentHashMap<UUID, List<UUID>> subscribers = new ConcurrentHashMap<>(); // fixme most of the issues with tardises on client when the world gets reloaded is because the subscribers dont get readded so the client stops getting informed, either save this somehow or make sure the client reasks on load.

    public final ConcurrentHashMap<UUID, List<UUID>> exterior_subscribers = new ConcurrentHashMap<>();
    public final ConcurrentHashMap<UUID, List<UUID>> interior_subscribers = new ConcurrentHashMap<>();

    public ServerTardisManager() {
        ServerPlayNetworking.registerGlobalReceiver(
                ClientTardisManager.ASK, (server, player, handler, buf, responseSender) -> {
                    UUID uuid = buf.readUuid();
                    if (player == null) return;
                    addSubscriberToTardis(player, uuid);
                    this.sendTardis(player, uuid);

                }
        );

        ServerPlayNetworking.registerGlobalReceiver(
                ClientTardisManager.LET_KNOW_UNLOADED, (server, player, handler, buf, responseSender) -> {
                    UUID uuid = buf.readUuid();
                    if (player == null) return;
                    removeSubscriberToTardis(player, uuid);
                }
        );

        ServerPlayNetworking.registerGlobalReceiver(
                ClientTardisManager.ASK_POS, (server, player, handler, buf, responseSender) -> {
                    BlockPos pos = buf.readBlockPos();
                    UUID uuid = null;
                    for (Tardis tardis : this.getLookup().values()) {
                        if (!tardis.getTravel().getPosition().equals(pos)) continue;

                        uuid = tardis.getUuid();
                    }
                    if (uuid == null)
                        return;
                    addSubscriberToTardis(player, uuid);
                    this.sendTardis(player, uuid);
                }
        );

        ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
            // force all dematting to go flight and all matting to go land
            for (Tardis tardis : this.getLookup().values()) {
                if (tardis.getTravel().getState() == TardisTravel.State.DEMAT) {
                    tardis.getTravel().toFlight();
                } else if (tardis.getTravel().getState() == TardisTravel.State.MAT) {
                    tardis.getTravel().forceLand();
                }

                tardis.getDoor().closeDoors();
            }

            this.reset();
        });
        ServerLifecycleEvents.SERVER_STARTED.register(server -> this.loadTardises());
        ServerTickEvents.END_SERVER_TICK.register(server -> {
            // fixme would this cause lag?
            for (Tardis tardis : ServerTardisManager.getInstance().getLookup().values()) {
                tardis.tick(server);
            }
        });
        ServerTickEvents.END_WORLD_TICK.register(world -> {
            // fixme lag?
            for (Tardis tardis : ServerTardisManager.getInstance().getLookup().values()) {
                tardis.tick(world);
            }
        });
        ServerTickEvents.START_SERVER_TICK.register(server -> {
            for (Tardis tardis : ServerTardisManager.getInstance().getLookup().values()) {
                tardis.startTick(server);
            }
        });
    }

    /**
     * Adds a subscriber to the Tardis
     * @param serverPlayerEntity PLAYER
     * @param tardisUUID TARDIS UUID
     */
    public void addSubscriberToTardis(ServerPlayerEntity serverPlayerEntity, UUID tardisUUID) {
        if (this.subscribers.containsKey(tardisUUID)) {
            this.subscribers.get(tardisUUID).add(serverPlayerEntity.getUuid());
        } else {
            List<UUID> subscriber_list = new CopyOnWriteArrayList<>();
            subscriber_list.add(serverPlayerEntity.getUuid());
            this.subscribers.put(tardisUUID, subscriber_list);
        }

    }

    /**
     * Adds an exterior subscriber to the Tardis.
     *
     * @param  player  the server player entity to add as a subscriber
     * @param  uuid    the UUID of the subscriber
     */
    public void addExteriorSubscriberToTardis(ServerPlayerEntity player, UUID uuid) {
        if (this.exterior_subscribers.containsKey(uuid)) {
            this.exterior_subscribers.get(uuid).add(player.getUuid());
        } else {
            List<UUID> subscriber_list = new CopyOnWriteArrayList<>();
            subscriber_list.add(player.getUuid());
            this.exterior_subscribers.put(uuid, subscriber_list);
        }
    }

    /**
     * Adds an interior subscriber to the Tardis.
     *
     * @param  player  the ServerPlayerEntity to add as a subscriber
     * @param  uuid    the UUID of the subscriber
     */
    public void addInteriorSubscriberToTardis(ServerPlayerEntity player, UUID uuid) {
        if (this.interior_subscribers.containsKey(uuid)) {
            this.interior_subscribers.get(uuid).add(player.getUuid());
        } else {
            List<UUID> subscriber_list = new CopyOnWriteArrayList<>();
            subscriber_list.add(player.getUuid());
            this.interior_subscribers.put(uuid, subscriber_list);
        }
    }

    /**
     * Removes the specified player from the exterior subscribers of the Tardis with the given UUID.
     *
     * @param  player  the player to remove
     * @param  uuid    the UUID of the Tardis
     */
    public void removeExteriorSubscriberToTardis(ServerPlayerEntity player, UUID uuid) {
        if (!this.exterior_subscribers.containsKey(uuid)) return;
        List<UUID> old_uuids = this.exterior_subscribers.get(uuid);
        int i_to_remove = -1;
        for (int i = 0; i < old_uuids.size(); i++) {
            if (old_uuids.get(i).equals(player.getUuid())) {
                i_to_remove = i;
                break;
            }
        }
        if (i_to_remove == -1) return;
        old_uuids.remove(i_to_remove);
        if (old_uuids.isEmpty()) {
            this.exterior_subscribers.remove(uuid);
        } else {
            this.exterior_subscribers.put(uuid, old_uuids);
        }
    }

    /**
     * Removes the interior subscriber with the specified UUID for the given player.
     *
     * @param  player  the server player entity
     * @param  uuid    the UUID of the interior subscriber to be removed
     */
    public void removeInteriorSubscriberToTardis(ServerPlayerEntity player, UUID uuid) {
        if (!this.interior_subscribers.containsKey(uuid)) return;
        List<UUID> old_uuids = this.interior_subscribers.get(uuid);
        int i_to_remove = -1;
        for (int i = 0; i < old_uuids.size(); i++) {
            if (old_uuids.get(i).equals(player.getUuid())) {
                i_to_remove = i;
                break;
            }
        }
        if (i_to_remove == -1) return;
        old_uuids.remove(i_to_remove);
        if (old_uuids.isEmpty()) {
            this.interior_subscribers.remove(uuid);
        } else {
            this.interior_subscribers.put(uuid, old_uuids);
        }
    }

    public void removePlayerFromAllTardis(ServerPlayerEntity serverPlayerEntity) {
        for (Map.Entry<UUID, List<UUID>> entry : this.exterior_subscribers.entrySet()) {
            removeSubscriberToTardis(serverPlayerEntity, entry.getKey());
        }
        for (Map.Entry<UUID, List<UUID>> entry : this.interior_subscribers.entrySet()) {
            removeSubscriberToTardis(serverPlayerEntity, entry.getKey());
        }
        for (Map.Entry<UUID, List<UUID>> entry : this.subscribers.entrySet()) {
            removeSubscriberToTardis(serverPlayerEntity, entry.getKey());
        }
    }

    /**
     * Removes a subscriber from the TARDIS
     * @param serverPlayerEntity the player to remove from the subscribers list
     * @param tardisUUID the UUID of the TARDIS
     */
    private void removeSubscriberToTardis(ServerPlayerEntity serverPlayerEntity, UUID tardisUUID) {
        if (!this.subscribers.containsKey(tardisUUID)) return; // If the Tardis does not have any subscribers ignore this

        List<UUID> old_uuids = this.subscribers.get(tardisUUID);
        int i_to_remove = -1;

        for (int i = 0; i < old_uuids.size(); i++) {
            if (old_uuids.get(i).equals(serverPlayerEntity.getUuid())) {
                i_to_remove = i;
                break;
            }
        }

        if (i_to_remove == -1) return; // If the player is not in the list ignore this

        old_uuids.remove(i_to_remove);
        if (old_uuids.isEmpty()) {
            this.subscribers.remove(tardisUUID);
        } else {
            this.subscribers.put(tardisUUID, old_uuids); // update the subscriber list in case any other subscriber was added or removed during this operation
        }
    }

    public ServerTardis create(AbsoluteBlockPos.Directed pos, ExteriorSchema exteriorType, ExteriorVariantSchema variantType, TardisDesktopSchema schema, boolean locked) {
        UUID uuid = UUID.randomUUID();

        ServerTardis tardis = new ServerTardis(uuid, pos, schema, exteriorType, variantType, locked);
        // tardis.setFuelCount(1000); // Default fuel count is 100 - cant be set here causes issues. set in PropertiesHandler instead
        //this.saveTardis(tardis);
        this.lookup.put(uuid, tardis);

        tardis.getTravel().placeExterior();
        tardis.getTravel().runAnimations();
        return tardis;
    }

    public Tardis getTardis(UUID uuid) {
        if (this.lookup.containsKey(uuid))
            return this.lookup.get(uuid);

        return this.loadTardis(uuid);
    }

    @Override
    public void loadTardis(UUID uuid, Consumer<Tardis> consumer) {
        consumer.accept(this.loadTardis(uuid));
    }

    private Tardis loadTardis(UUID uuid) {
        File file = ServerTardisManager.getSavePath(uuid);
        file.getParentFile().mkdirs();

        try {
            if (!file.exists())
                throw new IOException("Tardis file " + file + " doesn't exist!");

            String json = Files.readString(file.toPath());
            ServerTardis tardis = this.gson.fromJson(json, ServerTardis.class);
            this.lookup.put(tardis.getUuid(), tardis);

            return tardis;
        } catch (IOException e) {
            AITMod.LOGGER.warn("Failed to load tardis with uuid {}!", file);
            AITMod.LOGGER.warn(e.getMessage());
        }

        return null;
    }

    @Override
    public GsonBuilder init(GsonBuilder builder) {
        builder.registerTypeAdapter(SerialDimension.class, SerialDimension.serializer());
        return builder;
    }

    public void saveTardis(Tardis tardis) {
        /*File savePath = ServerTardisManager.getSavePath(tardis);
        savePath.getParentFile().mkdirs();

        try {
            Files.writeString(savePath.toPath(), this.gson.toJson(tardis, ServerTardis.class));
        } catch (IOException e) {
            AITMod.LOGGER.warn("Couldn't save Tardis {}", tardis.getUuid());
            AITMod.LOGGER.warn(e.getMessage());
        }*/
        this.saveTardisAsync(tardis);
    }

    public void saveTardisAsync(Tardis tardis) {
        CompletableFuture.runAsync(() -> {
            File savePath = ServerTardisManager.getSavePath(tardis);
            savePath.getParentFile().mkdirs();

            try {
                Files.writeString(savePath.toPath(),
                        this.gson.toJson(tardis, ServerTardis.class));
            } catch (IOException e) {
                AITMod.LOGGER.warn("Couldn't save Tardis {}", tardis.getUuid());
                AITMod.LOGGER.warn(e.getMessage());
            }
        });
    }

    public void saveTardises() {
        List<Tardis> tardises = new CopyOnWriteArrayList<>(getLookup().values());

        // Split the tardises into multiple groups
        int numThreads = Runtime.getRuntime().availableProcessors();
        int batchSize = (int) Math.ceil((double) tardises.size() / numThreads);

        // Create an ExecutorService with a thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(numThreads);

        List<Callable<Void>> tasks = new ArrayList<>();

        // Create a Callable for each batch of tardises
        for (int i = 0; i < tardises.size(); i += batchSize) {
            int endIndex = Math.min(i + batchSize, tardises.size());
            List<Tardis> batch = tardises.subList(i, endIndex);

            // Create a Callable to save the batch of tardises
            Callable<Void> task = () -> {
                for (Tardis tardis : batch) {
                    // Save the tardis
                    this.saveTardis(tardis);
                }
                return null;
            };

            tasks.add(task);
        }

        try {
            // Submit all the tasks to the ExecutorService
            List<Future<Void>> futures = executorService.invokeAll(tasks);

            // Wait for all the tasks to complete
            for (Future<Void> future : futures) {
                future.get();
            }
        } catch (InterruptedException | ExecutionException e) {
            // Handle any exceptions that may occur
            AITMod.LOGGER.warn("Failed to save tardises []" + e.getMessage());
        } finally {
            // Shutdown the ExecutorService
            executorService.shutdown();
        }
    }

    public void saveTardis() {
        for (Tardis tardis : this.lookup.values()) {
            this.saveTardis(tardis);
        }
    }

    public void sendToSubscribers(Tardis tardis) {
        if (tardis == null) return;
        if (!this.subscribers.containsKey(tardis.getUuid())) return;
//        if (!this.subscribers.containsKey(tardis.getUuid())) this.subscribeEveryone(tardis);
        MinecraftServer mc = TardisUtil.getServer();

        Map<UUID, List<UUID>> subscribersCopy = new HashMap<>(this.subscribers);
        List<UUID> tardisSubscribers = new CopyOnWriteArrayList<>(subscribersCopy.getOrDefault(tardis.getUuid(), Collections.emptyList()));

        for (UUID uuid : tardisSubscribers) {
            ServerPlayerEntity player = mc.getPlayerManager().getPlayer(uuid);
            this.sendTardis(player, tardis);
        }
    }

    private void sendTardis(ServerPlayerEntity player, UUID uuid) {
        if (player == null) return;
        this.sendTardis(player, this.getTardis(uuid));
    }

    private void sendTardis(ServerPlayerEntity player, Tardis tardis) {
        if (player == null) return;
        this.sendTardis(player, tardis.getUuid(), this.gson.toJson(tardis, ServerTardis.class));
    }

    private void sendTardis(ServerPlayerEntity player, UUID uuid, String json) {
        if (player == null) return;
        PacketByteBuf data = PacketByteBufs.create();
        data.writeUuid(uuid);
        data.writeString(json);

        ServerPlayNetworking.send(player, SEND, data);
    }

    @Override
    public void reset() {
        this.subscribers.clear();

        this.saveTardises();
        super.reset();
    }

    private static File getSavePath(UUID uuid) {
        // TODO: maybe, make WorldSavePath.AIT?
        return new File(TardisUtil.getServer().getSavePath(WorldSavePath.ROOT) + "ait/" + uuid + ".json");
    }

    private static File getSavePath(Tardis tardis) {
        return ServerTardisManager.getSavePath(tardis.getUuid());
    }

    public static ServerTardisManager getInstance() {
        //System.out.println("getInstance() = " + instance);
        return instance;
    }

    public void loadTardises() {
        Path savePath = TardisUtil.getServer().getSavePath(WorldSavePath.ROOT).resolve("ait");

        File[] saved = savePath.toFile().listFiles((dir, name) ->
                name.toLowerCase().endsWith(".json") && !new File(dir, name).isDirectory());

        if (saved == null) {
            return;
        }

        for (String name : Stream.of(saved)
                .map(File::getName)
                .collect(Collectors.toSet())) {

            UUID uuid = UUID.fromString(name.substring(name.lastIndexOf("/") + 1, name.lastIndexOf(".")));
            this.loadTardis(uuid);
        }
    }
}

src/main/java/mdteam/ait/tardis/Tardis.java
public class Tardis {
    // this is starting to get a little bloated..

    private final TardisTravel travel;
    private final UUID uuid;
    private TardisDesktop desktop;
    private final TardisExterior exterior;
    private TardisHandlersManager handlers;
    private boolean dirty = false;

    public Tardis(UUID uuid, AbsoluteBlockPos.Directed pos, TardisDesktopSchema schema, ExteriorSchema exteriorType, ExteriorVariantSchema variant) {
        this(uuid, tardis -> new TardisTravel(tardis, pos), tardis -> new TardisDesktop(tardis, schema), (tardis) -> new TardisExterior(tardis, exteriorType, variant), false);
    }

    protected Tardis(UUID uuid, Function<Tardis, TardisTravel> travel, Function<Tardis, TardisDesktop> desktop, Function<Tardis, TardisExterior> exterior, boolean locked) {
        this.uuid = uuid;
        this.travel = travel.apply(this);
        this.desktop = desktop.apply(this);
        this.exterior = exterior.apply(this);
        this.handlers = new TardisHandlersManager(uuid);
    }

    public UUID getUuid() {
        return uuid;
    }

    public void setDesktop(TardisDesktop desktop) {
        this.desktop = desktop;
    }

    public TardisDesktop getDesktop() {
        return desktop;
    }

    public TardisExterior getExterior() {
        return exterior;
    }

    public DoorHandler getDoor() {
        return this.getHandlers().getDoor();
    }

    // dont use this
    public void setLockedTardis(boolean bool) {
        this.getDoor().setLocked(bool);
    }

    public boolean getLockedTardis() {
        return this.getDoor().locked();
    }

    public TardisTravel getTravel() {
        return travel;
    }

    /**
     * Retrieves the TardisHandlersManager instance associated with the given UUID.
     *
     * @return TardisHandlersManager instance or null if it doesn't exist
     */
    public TardisHandlersManager getHandlers() {
        if (handlers == null) {
            handlers = new TardisHandlersManager(getUuid());
        }

        return handlers;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() == null) return false;
        Tardis tardis = (Tardis) o;
        return uuid.equals(tardis.uuid);
    }

    @Override
    public int hashCode() {
        return Objects.hash(uuid);
    }

    // fuel - because getHandlers() blah blah is annoying me
    public double addFuel(double fuel) {
        return this.getHandlers().getFuel().addFuel(fuel);
    }
    public void removeFuel(double fuel) {
        this.getHandlers().getFuel().removeFuel(fuel);
    }
    public double getFuel() {
        return this.getHandlers().getFuel().getFuel();
    }

    public void setFuelCount(double i) {
        this.getHandlers().getFuel().setFuelCount(i);
    }
    public boolean isRefueling() {
        return this.getHandlers().getFuel().isRefueling();
    }
    public void setRefueling(boolean b) {
        this.getHandlers().getFuel().setRefueling(b);
    }

    public void setIsInDanger(boolean danger) {
        this.getHandlers().getHADS().setIsInDanger(danger);
    }

    // unlock destop stuff
    // kill me.
    public boolean isDesktopUnlocked(TardisDesktopSchema schema) {
        return PropertiesHandler.isSchemaUnlocked(getHandlers().getProperties(), schema);
    }
    public void unlockDesktop(TardisDesktopSchema schema) {
        PropertiesHandler.setSchemaUnlocked(getHandlers().getProperties(), schema, true);
        this.markDirty();
    }

    // for now this just checks that the exterior is the coral growth, which is bad. but its fine for first beta
    // this should stop basic features of the tardis from happening
    public boolean isGrowth() {
        return hasGrowthExterior() || hasGrowthDesktop();
    }
    public boolean hasGrowthExterior() {
        return getExterior().getVariant().equals(ExteriorVariantRegistry.CORAL_GROWTH);
    }
    public boolean hasGrowthDesktop() {
        return getDesktop().getSchema().equals(DesktopRegistry.DEFAULT_CAVE);
    }

    public boolean hasPower() {
        return PropertiesHandler.getBool(this.getHandlers().getProperties(), PropertiesHandler.HAS_POWER);
    }
    public void disablePower() {
        if (!hasPower()) return;

        //PropertiesHandler.set(this.getHandlers().getProperties(), PropertiesHandler.POWER_DELTA, MAX_POWER_DELTA_TICKS);
        PropertiesHandler.setBool(this.getHandlers().getProperties(), PropertiesHandler.HAS_POWER, false);
        TardisEvents.LOSE_POWER.invoker().onLosePower(this);
        this.markDirty();
    }
    public void enablePower() {
        if (getFuel() <= (0.01 * FuelHandler.TARDIS_MAX_FUEL)) return; // cant enable power if not enough fuel
        if (isSiegeBeingHeld()) return; // cant re-enable while being held, this may become OP tho
        if (isSiegeMode()) setSiegeMode(false);
        if (hasPower()) return;

        //PropertiesHandler.set(this.getHandlers().getProperties(), PropertiesHandler.POWER_DELTA, 0);
        PropertiesHandler.setBool(this.getHandlers().getProperties(), PropertiesHandler.HAS_POWER, true);
        TardisEvents.REGAIN_POWER.invoker().onRegainPower(this);
        this.markDirty();
    }
    public void togglePower() {
        if (hasPower())
            disablePower();
        else
            enablePower();
    }

    public boolean isSiegeMode() {
        return this.getHandlers().getSiege().isSiegeMode();
    }
    public void setSiegeMode(boolean b) {
        this.getHandlers().getSiege().setSiegeMode(b);
    }
    public boolean isSiegeBeingHeld() {
        return this.getHandlers().getSiege().isSiegeBeingHeld();
    }
    public void setSiegeBeingHeld(UUID b) {
        this.getHandlers().getSiege().setSiegeBeingHeld(b);
    }
    public int getTimeInSiegeMode() {
        return this.getHandlers().getSiege().getTimeInSiegeMode();
    }

    public AbsoluteBlockPos.Directed position() {
        return this.getTravel().getPosition();
    }
    public AbsoluteBlockPos.Directed destination() {
        return this.getTravel().getDestination();
    }

    /**
     * Called at the end of a servers tick
     *
     * @param server the server being ticked
     */
    public void tick(MinecraftServer server) {
        // most of the logic is in the handlers, so we can just disable them if we're a growth
        // if (!isGrowth())
        //     this.getHandlers().tick(server);

        // @TODO if tnt explodes in the interior (near the console), then it should crash

        if (isGrowth() && getDoor().isBothClosed() && !getHandlers().getInteriorChanger().isGenerating())
            getDoor().openDoors();
        if (isGrowth() && getDoor().locked() && !getHandlers().getInteriorChanger().isGenerating())
            getDoor().setLocked(false);

        if (isSiegeMode() && !getDoor().locked())
            getDoor().setLocked(true);

        this.getHandlers().tick(server);

        // im sure this is great for your server performace
        if (TardisChunkUtil.shouldExteriorChunkBeForced(this) && !TardisChunkUtil.isExteriorChunkForced(this)) {
            TardisChunkUtil.forceLoadExteriorChunk(this);
        } else if (!TardisChunkUtil.shouldExteriorChunkBeForced(this) && TardisChunkUtil.isExteriorChunkForced(this)) {
            TardisChunkUtil.stopForceExteriorChunk(this);
        }

        // autoland stuff
        // if (getTravel().getState() == TardisTravel.State.FLIGHT && PropertiesHandler.getBool(getHandlers().getProperties(), PropertiesHandler.AUTO_LAND)) {
        //     getTravel().materialise();
        // }

        // fixme nuh uh i dont like it when it locks on land it makes me sadge, instead lock if it was locked - Loqor

        /*if (PropertiesHandler.getBool(getHandlers().getProperties(), PropertiesHandler.IS_FALLING) && !getHandlers().getDoor().locked()) {
            DoorHandler.lockTardis(true, this, null, true);
        }*/
        if (PropertiesHandler.getBool(getHandlers().getProperties(), PropertiesHandler.IS_FALLING)) {
            DoorHandler.lockTardis(true, this, null, true);
        }
        this.getTravel().tick(server);
    }

    /**
     * Called at the end of a worlds tick
     *
     * @param world the world being ticked
     */
    public void tick(ServerWorld world) {
    }

    /**
     * Called at the end of a clients tick, ONLY FOR CLIENT STUFF!!
     *
     * @param client the remote being ticked
     */
    public void tick(MinecraftClient client) { // fixme should likely be in ClientTardis instead, same with  other server-only things should be in ServerTardis
        // referencing client stuff where it COULD be server causes problems
        if(client.player != null &&
                ClientTardisUtil.isPlayerInATardis() && ClientTardisUtil.getCurrentTardis().equals(this) &&
                this.getTravel() != null && this.getTravel().getState() != TardisTravel.State.LANDED) {
            /*if (ClientShakeUtil.shouldShake(this)) */
            ClientShakeUtil.shakeFromConsole();
        }

        ClientTardisUtil.tickPowerDelta();
        ClientTardisUtil.tickAlarmDelta();
    }

    public boolean isDirty() {
        return dirty;
    }

    public void markDirty() {
        dirty = true;
    }

    /**
     * Called at the START of a servers tick, ONLY to be used for syncing data to avoid comodification errors
     *
     * @param server the current minecraft server
     */
    public void startTick(MinecraftServer server) {
        if (this instanceof ServerTardis && isDirty()) {
            ((ServerTardis) this).sync();
            dirty = false;
        }
    }

    public boolean isInDanger() {
        return this.getHandlers().getHADS().isInDanger();
    }
}

src/main/java/mdteam/ait/core/AITBlockEntityTypes.java
public class AITBlockEntityTypes implements NeptuneBlockEntityInit {

    public static BlockEntityType<ExteriorBlockEntity> EXTERIOR_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(ExteriorBlockEntity::new, AITBlocks.EXTERIOR_BLOCK).build();
    public static BlockEntityType<DoorBlockEntity> DOOR_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(DoorBlockEntity::new, AITBlocks.DOOR_BLOCK).build();
    public static BlockEntityType<ConsoleBlockEntity> CONSOLE_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(ConsoleBlockEntity::new, AITBlocks.CONSOLE).build();
    public static BlockEntityType<ConsoleGeneratorBlockEntity> CONSOLE_GENERATOR_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(ConsoleGeneratorBlockEntity::new, AITBlocks.CONSOLE_GENERATOR).build();
    public static BlockEntityType<CoralBlockEntity> CORAL_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(CoralBlockEntity::new, AITBlocks.CORAL_PLANT).build();
    public static BlockEntityType<AITRadioBlockEntity> AIT_RADIO_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(AITRadioBlockEntity::new, AITBlocks.RADIO).build();
}

src/main/java/mdteam/ait/core/blockentities/ConsoleBlockEntity.java
public static ConsoleVariantSchema nextVariant(ConsoleVariantSchema current) {
    List<ConsoleVariantSchema> list = ConsoleVariantRegistry.withParent(current.parent());

    int idx = list.indexOf(current);
    if (idx < 0 || idx+1 == list.size()) return list.get(0);
    return list.get(idx + 1);
}

src/main/java/mdteam/ait/tardis/variant/console/ConsoleVariantSchema.java
public abstract class ConsoleVariantSchema {
    private final Identifier parent;
    private final Identifier id;

    protected ConsoleVariantSchema(Identifier parent, Identifier id) {
        this.parent = parent;
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() == null) return false;

        ConsoleVariantSchema that = (ConsoleVariantSchema) o;

        return id.equals(that.id);
    }

    public ConsoleSchema parent() { return ConsoleRegistry.REGISTRY.get(this.parent); }
    public Identifier id() { return id; }
    public Identifier clientId() { return id().withPath(id().getPath() + "_client"); }

    public static Object serializer() {
        return new Serializer();
    }

    private static class Serializer implements JsonSerializer<ConsoleVariantSchema>, JsonDeserializer<ConsoleVariantSchema> {

        @Override
        public ConsoleVariantSchema deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            Identifier id;

            try {
                id = new Identifier(json.getAsJsonPrimitive().getAsString());
            } catch (InvalidIdentifierException e) {
                id = new Identifier(AITMod.MOD_ID, "console/borealis");
            }

            return ConsoleVariantRegistry.REGISTRY.get(id);
        }

        @Override
        public JsonElement serialize(ConsoleVariantSchema src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.id().toString());
        }
    }
}

src/main/java/mdteam/ait/tardis/util/TardisUtil.java
public static boolean isClient() {
    return !TardisUtil.isServer();
}

src/main/java/mdteam/ait/core/blockentities/ConsoleBlockEntity.java
public static ConsoleSchema nextConsole(ConsoleSchema current) {
    List<ConsoleSchema> list = ConsoleRegistry.REGISTRY.stream().toList();

    int idx = list.indexOf(current);
    if (idx < 0 || idx+1 == list.size()) return list.get(0);
    return list.get(idx + 1);
}

src/main/java/mdteam/ait/core/blocks/ConsoleGeneratorBlock.java
public class ConsoleGeneratorBlock extends HorizontalDirectionalBlock implements BlockEntityProvider {

    public ConsoleGeneratorBlock(Settings settings) {
        super(settings);
    }

    @Nullable
    @Override
    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
        return new ConsoleGeneratorBlockEntity(pos, state);
    }

    @Override
    public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
        return super.getAppearance(state, renderView, pos, side, sourceState, sourcePos);
    }

    @Override
    public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
        BlockEntity blockEntity = world.getBlockEntity(pos);
        if (blockEntity instanceof ConsoleGeneratorBlockEntity consoleGeneratorBlockEntity)
            consoleGeneratorBlockEntity.useOn(world, player.isSneaking(), player);
        return ActionResult.SUCCESS;
    }
}

src/main/java/mdteam/ait/AITMod.java
public class AITMod implements ModInitializer {
    public static final String MOD_ID = "ait";
    public static final Logger LOGGER = LoggerFactory.getLogger("ait");
    public static final Boolean DEBUG = true;

    public static final AITConfig AIT_CONFIG = AITConfig.createAndLoad(); // if this doesnt exist for you run data gen
    public static final NeptuneItemGroup AIT_ITEM_GROUP = new NeptuneItemGroup(new Identifier(AITMod.MOD_ID, "item_group"), AITItems.TARDIS_ITEM.getDefaultStack());
    public static final ComponentKey<RadioNBTComponent> RADIONBT =
            ComponentRegistry.getOrCreate(new Identifier(AITMod.MOD_ID, "radionbt"), RadioNBTComponent.class);

    @Override
    public void onInitialize() {
        ServerAITNetworkManager.init();
        ConsoleRegistry.init();
        DesktopRegistry.init();
        ExteriorRegistry.init();
        HumsRegistry.init();
        CreakRegistry.init();
        SequenceRegistry.init();

        // These 3 have client registries which also need registering to.
        ConsoleVariantRegistry.init();
        ExteriorVariantRegistry.init();
        DoorRegistry.init();

        NeptuneInitHandler.register(AITItems.class, MOD_ID);
        NeptuneInitHandler.register(AITBlocks.class, MOD_ID);
        NeptuneInitHandler.register(AITSounds.class, MOD_ID);
        NeptuneInitHandler.register(AITBlockEntityTypes.class, MOD_ID);
        NeptuneInitHandler.register(AITEntityTypes.class, MOD_ID);

        TardisUtil.init();
        TardisManager.getInstance();
        TardisManager.init();
        RiftChunkManager.init();
        TardisCriterions.init();

        entityAttributeRegister();

        // ip support
        if (DependencyChecker.hasPortals())
            PortalsHandler.init();

        if (DependencyChecker.hasRegeneration())
            RegenHandler.init();

        CommandRegistrationCallback.EVENT.register(((dispatcher, registryAccess, environment) -> {
            TeleportInteriorCommand.register(dispatcher);
            UnlockInteriorsCommand.register(dispatcher);
            SummonTardisCommand.register(dispatcher);
            SetLockedCommand.register(dispatcher);
            // SetHumCommand.register(dispatcher);
            SetFuelCommand.register(dispatcher);
            AddFuelCommand.register(dispatcher);
            RemoveFuelCommand.register(dispatcher);
            ToggleHumCommand.register(dispatcher);
            ToggleAlarmCommand.register(dispatcher);
            ToggleSiegeModeCommand.register(dispatcher);
            RiftChunkCommand.register(dispatcher);
            RealWorldCommand.register(dispatcher);
        }));

        ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register(((blockEntity, world) -> {
            // fixme this doesnt seem to run??
            if (blockEntity instanceof ConsoleBlockEntity console) {
                console.markNeedsSyncing();
            }
        }));

        TardisEvents.LANDED.register((tardis -> {
            // stuff for resetting the ExteriorAnimation
            if (tardis.getTravel().getPosition().getWorld().getBlockEntity(tardis.getTravel().getExteriorPos()) instanceof ExteriorBlockEntity entity) {
                entity.getAnimation().setupAnimation(tardis.getTravel().getState());
            }
        }));

        TardisEvents.DEMAT.register((tardis -> {
            if (tardis.isGrowth() || tardis.getHandlers().getInteriorChanger().isGenerating() || PropertiesHandler.getBool(tardis.getHandlers().getProperties(), PropertiesHandler.HANDBRAKE) || PropertiesHandler.getBool(tardis.getHandlers().getProperties(), PropertiesHandler.IS_FALLING) || tardis.isRefueling())
                return true; // cancelled

            if (tardis.getDoor().isOpen() /*|| !tardis.getDoor().locked()*/)
                return true;

            for (PlayerEntity player : TardisUtil.getPlayersInInterior(tardis)) {
                TardisCriterions.TAKEOFF.trigger((ServerPlayerEntity) player);
            }
            return false;
        }));

        TardisEvents.MAT.register((tardis -> {
            // Check that the tardis has finished flight
            boolean flightDone = tardis.getHandlers().getFlight().hasFinishedFlight();

            // Check if the Tardis is on cooldown
            boolean isCooldown = FlightUtil.isMaterialiseOnCooldown(tardis);

            // Check if the destination is already occupied
            boolean isDestinationOccupied = !tardis.getTravel().getPosition().equals(tardis.getTravel().getDestination()) && !tardis.getTravel().checkDestination();

            return /*!flightDone ||*/ isCooldown || isDestinationOccupied;
        }));

        TardisEvents.CRASH.register((tardis -> {
            for (PlayerEntity player : TardisUtil.getPlayersInInterior(tardis)) {
                TardisCriterions.CRASH.trigger((ServerPlayerEntity) player);
            }
        }));

        TardisEvents.OUT_OF_FUEL.register(Tardis::disablePower);
        TardisEvents.LOSE_POWER.register((tardis -> {
            if (tardis.getDesktop().getConsolePos() != null) {
                TardisUtil.getTardisDimension().playSound(null, tardis.getDesktop().getConsolePos(), AITSounds.SHUTDOWN, SoundCategory.AMBIENT, 10f, 1f);
            }

            // disabling protocols
            PropertiesHandler.setBool(tardis.getHandlers().getProperties(), PropertiesHandler.AUTO_LAND, false);
            PropertiesHandler.setBool(tardis.getHandlers().getProperties(), PropertiesHandler.ANTIGRAVS_ENABLED, false);
            PropertiesHandler.setBool(tardis.getHandlers().getProperties(), PropertiesHandler.HAIL_MARY, false);
            PropertiesHandler.setBool(tardis.getHandlers().getProperties(), PropertiesHandler.HADS_ENABLED, false);
        }));
        TardisEvents.REGAIN_POWER.register((tardis -> {
            if (tardis.getDesktop().getConsolePos() != null) {
                TardisUtil.getTardisDimension().playSound(null, tardis.getDesktop().getConsolePos(), AITSounds.POWERUP, SoundCategory.AMBIENT, 10f, 1f);
            }
        }));

        ServerPlayNetworking.registerGlobalReceiver(ConsoleBlockEntity.ASK, ((server, player, handler, buf, responseSender) -> {
            if (player.getServerWorld().getRegistryKey() != AITDimensions.TARDIS_DIM_WORLD) return;

            BlockPos consolePos = buf.readBlockPos();
            // fixme the gotten block entity is always null, shit.
            if (player.getServerWorld().getBlockEntity(consolePos) instanceof ConsoleBlockEntity console)
                console.markNeedsSyncing();
        }));

        ServerPlayNetworking.registerGlobalReceiver(ServerHumHandler.RECEIVE, ((server, player, handler, buf, responseSender) -> {
            Tardis tardis = ServerTardisManager.getInstance().getTardis(buf.readUuid());
            HumSound hum = HumSound.fromName(buf.readString(), buf.readString());

            if (tardis == null || hum == null) return;

            tardis.getHandlers().getHum().setHum(hum);
        }));

        ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
            DesktopRegistry.syncToClient(handler.getPlayer());
        });

        ServerPlayNetworking.registerGlobalReceiver(TardisDesktop.CACHE_CONSOLE, (server, player, handler, buf, responseSender) -> {
            Tardis tardis = ServerTardisManager.getInstance().getTardis(buf.readUuid());
            TardisUtil.getServer().execute(() -> {
                if (tardis == null) return;
                tardis.getDesktop().cacheConsole();
            });
        });

        AIT_ITEM_GROUP.initialize();
    }

    public void entityAttributeRegister() {
        FabricDefaultAttributeRegistry.register(AITEntityTypes.CONTROL_ENTITY_TYPE, ConsoleControlEntity.createControlAttributes());
    }

    public static final Identifier OPEN_SCREEN_TARDIS = new Identifier(AITMod.MOD_ID, "open_screen_tardis"); // fixes "AITModClient in env type SERVER"

    public static void openScreen(ServerPlayerEntity player, int id, UUID tardis) {
        PacketByteBuf buf = PacketByteBufs.create();
        buf.writeInt(id);
        buf.writeUuid(tardis);
        ServerPlayNetworking.send(player, OPEN_SCREEN_TARDIS, buf);
    }
}

src/main/java/mdteam/ait/tardis/console/ConsoleSchema.java
public abstract class ConsoleSchema {
    private final Identifier id;
    private final String name;

    protected ConsoleSchema(Identifier id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() == null) return false;

        ConsoleSchema that = (ConsoleSchema) o;

        return id.equals(that.id);
    }

    public Identifier id() {
        return this.id;
    }
    public String name() { return this.name; }

    @Override
    public String toString() {
        return this.name();
    }

    //@TODO protocol abstraction with numbered letters

    public abstract ControlTypes[] getControlTypes(); // fixme this kinda sucks idk

    public static Object serializer() {
        return new Serializer();
    }

    private static class Serializer implements JsonSerializer<ConsoleSchema>, JsonDeserializer<ConsoleSchema> {

        @Override
        public ConsoleSchema deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            Identifier id;

            try {
                id = new Identifier(json.getAsJsonPrimitive().getAsString());
            } catch (InvalidIdentifierException e) {
                id = CapsuleExterior.REFERENCE;
            }

            return ConsoleRegistry.REGISTRY.get(id);
        }

        @Override
        public JsonElement serialize(ConsoleSchema src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.id().toString());
        }
    }
}

Answer: if (variant == null) {

<think>
The method getConsoleVariant() is being defined. Following the pattern of getConsoleSchema(), the first line inside the method should check whether the stored variant identifier is null and, if so, initialize it. Therefore the next line after the opening brace is the null‑check condition.
</think>
if (variant == null) {


Input:
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.iterator.TLongIterator;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapAddressReaderAdapter.AddressRegion;
import net.osmand.binary.BinaryMapAddressReaderAdapter.CitiesBlock;
import net.osmand.binary.BinaryMapIndexReader.MapIndex;
import net.osmand.binary.BinaryMapIndexReader.MapObjectStat;
import net.osmand.binary.BinaryMapIndexReader.MapRoot;
import net.osmand.binary.BinaryMapIndexReader.SearchFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
import net.osmand.binary.BinaryMapIndexReader.TagValuePair;
import net.osmand.binary.BinaryMapPoiReaderAdapter.PoiRegion;
import net.osmand.binary.BinaryMapPoiReaderAdapter.PoiSubType;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteRegion;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteSubregion;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule;
import net.osmand.binary.BinaryMapTransportReaderAdapter.TransportIndex;
import net.osmand.data.Amenity;
import net.osmand.data.Building;
import net.osmand.data.City;
import net.osmand.data.MapObject;
import net.osmand.data.Street;
import net.osmand.osm.PoiCategory;
import net.osmand.util.MapUtils;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.WireFormat;

Context:
src/net/osmand/binary/BinaryInspector.java
				} else if (part instanceof RouteRegion) {
					ous.writeTag(OsmandOdb.OsmAndStructure.ROUTINGINDEX_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED);
					map = "Routing";
				} else {
					throw new UnsupportedOperationException();
				}
				writeInt(ous, part.getLength());
				copyBinaryPart(ous, BUFFER_TO_READ, raf, part.getFilePointer(), part.getLength());
				System.out.println(MessageFormat.format("{2} part {0} is extracted {1} bytes",
						new Object[]{part.getName(), part.getLength(), map}));

			}
		}

		ous.writeInt32(OsmandOdb.OsmAndStructure.VERSIONCONFIRM_FIELD_NUMBER, version);
		ous.flush();
		fout.close();

		return list;
	}

	public static void copyBinaryPart(CodedOutputStream ous, byte[] BUFFER, RandomAccessFile raf, long fp, int length)
			throws IOException {
		raf.seek(fp);
		int toRead = length;
		while (toRead > 0) {
			int read = raf.read(BUFFER);
			if (read == -1) {
				throw new IllegalArgumentException("Unexpected end of file");
			}
			if (toRead < read) {
				read = toRead;
			}
			ous.writeRawBytes(BUFFER, 0, read);
			toRead -= read;
		}
	}

	protected  String formatBounds(int left, int right, int top, int bottom){
		double l = MapUtils.get31LongitudeX(left);
		double r = MapUtils.get31LongitudeX(right);
		double t = MapUtils.get31LatitudeY(top);
		double b = MapUtils.get31LatitudeY(bottom);
		return formatLatBounds(l, r, t, b);
	}

	protected  String formatLatBounds(double l, double r, double t, double b){
		MessageFormat format = new MessageFormat("(left top - right bottom) : {0,number,#.####}, {1,number,#.####} NE - {2,number,#.####}, {3,number,#.####} NE", new Locale("EN", "US"));
		return format.format(new Object[]{l, t, r, b});
	}

	public  void printFileInformation(String fileName) throws IOException {
		File file = new File(fileName);
		if(!file.exists()){
			println("Binary OsmAnd index " + fileName + " was not found.");
			return;
		}
		printFileInformation(file);
	}

	public void printFileInformation(File file) throws IOException {
		RandomAccessFile r = new RandomAccessFile(file.getAbsolutePath(), "r");
		printFileInformation(r, file);
	}

	public  void printFileInformation(RandomAccessFile r, File file ) throws IOException {
		String filename = file.getName();
		try {
			BinaryMapIndexReader index = new BinaryMapIndexReader(r, file);
			int i = 1;
			println("Binary index " + filename + " version = " + index.getVersion() +" edition = " + new Date(index.getDateCreated()));
			for(BinaryIndexPart p : index.getIndexes()){
				String partname = "";
				if(p instanceof MapIndex ){
					partname = "Map";
				} else if(p instanceof TransportIndex){
					partname = "Transport";
				} else if(p instanceof RouteRegion){
					partname = "Routing";
				} else if(p instanceof PoiRegion){
					partname = "Poi";
				} else if(p instanceof AddressRegion){
					partname = "Address";
				}
				String name = p.getName() == null ? "" : p.getName();
				println(MessageFormat.format("{0} {1} data {3} - {2,number,#} bytes",
						new Object[]{i, partname, p.getLength(), name}));
				if(p instanceof TransportIndex){
					TransportIndex ti = ((TransportIndex) p);
					int sh = (31 - BinaryMapIndexReader.TRANSPORT_STOP_ZOOM);
					println("\tBounds " + formatBounds(ti.getLeft() << sh, ti.getRight() << sh,
							ti.getTop() << sh, ti.getBottom() << sh));
				} else if(p instanceof RouteRegion){
					RouteRegion ri = ((RouteRegion) p);
					println("\tBounds " + formatLatBounds(ri.getLeftLongitude(), ri.getRightLongitude(),
							ri.getTopLatitude(), ri.getBottomLatitude()));
					if((vInfo != null && vInfo.isVrouting())){
						printRouteDetailInfo(index, (RouteRegion) p);
					}
				} else if(p instanceof MapIndex){
					MapIndex m = ((MapIndex) p);
					int j = 1;
					for(MapRoot mi : m.getRoots()){
						println(MessageFormat.format("\t{4}.{5} Map level minZoom = {0}, maxZoom = {1}, size = {2,number,#} bytes \n\t\tBounds {3}",
								new Object[] {
								mi.getMinZoom(), mi.getMaxZoom(), mi.getLength(),
								formatBounds(mi.getLeft(), mi.getRight(), mi.getTop(), mi.getBottom()),
								i, j++}));
					}
					if((vInfo != null && vInfo.isVmap())){
						printMapDetailInfo(index, m);
					}
				} else if(p instanceof PoiRegion && (vInfo != null && vInfo.isVpoi())){
					printPOIDetailInfo(vInfo, index, (PoiRegion) p);
				} else if (p instanceof AddressRegion) {
					List<CitiesBlock> cities = ((AddressRegion) p).cities;

src/net/osmand/binary/BinaryMapIndexReader.java
public static class MapIndex extends BinaryIndexPart {
	List<MapRoot> roots = new ArrayList<MapRoot>();

	Map<String, Map<String, Integer> > encodingRules = new HashMap<String, Map<String, Integer> >();
	public TIntObjectMap<TagValuePair> decodingRules = new TIntObjectHashMap<TagValuePair>();
	public int nameEncodingType = 0;
	public int nameEnEncodingType = -1;
	public int refEncodingType = -1;
	public int coastlineEncodingType = -1;
	public int coastlineBrokenEncodingType = -1;
	public int landEncodingType = -1;
	public int onewayAttribute = -1;
	public int onewayReverseAttribute = -1;
	public TIntHashSet positiveLayers = new TIntHashSet(2);
	public TIntHashSet negativeLayers = new TIntHashSet(2);

	public Integer getRule(String t, String v){
		Map<String, Integer> m = encodingRules.get(t);
		if(m != null){
			return m.get(v);
		}
		return null;
	}

	public List<MapRoot> getRoots() {
		return roots;
	}

	public TagValuePair decodeType(int type){
		return decodingRules.get(type);
	}

	public void finishInitializingTags() {
		int free = decodingRules.size() * 2 + 1;
		coastlineBrokenEncodingType = free++;
		initMapEncodingRule(0, coastlineBrokenEncodingType, "natural", "coastline_broken");
		if(landEncodingType == -1){
			landEncodingType = free++;
			initMapEncodingRule(0, landEncodingType, "natural", "land");
		}
	}

	public boolean isRegisteredRule(int id) {
		return decodingRules.containsKey(id);
	}

	public void initMapEncodingRule(int type, int id, String tag, String val) {
		if(!encodingRules.containsKey(tag)){
			encodingRules.put(tag, new HashMap<String, Integer>());
		}
		encodingRules.get(tag).put(val, id);
		if(!decodingRules.containsKey(id)){
			decodingRules.put(id, new TagValuePair(tag, val, type));
		}

		if("name".equals(tag)){
			nameEncodingType = id;
		} else if("natural".equals(tag) && "coastline".equals(val)){
			coastlineEncodingType = id;
		} else if("natural".equals(tag) && "land".equals(val)){
			landEncodingType = id;
		} else if("oneway".equals(tag) && "yes".equals(val)){
			onewayAttribute = id;
		} else if("oneway".equals(tag) && "-1".equals(val)){
			onewayReverseAttribute = id;
		} else if("ref".equals(tag)){
			refEncodingType = id;
		} else if("name:en".equals(tag)){
			nameEnEncodingType = id;
		} else if("tunnel".equals(tag)){
			negativeLayers.add(id);
		} else if("bridge".equals(tag)){
			positiveLayers.add(id);
		} else if("layer".equals(tag)){
			if(val != null && !val.equals("0") && val.length() > 0) {
				if(val.startsWith("-")) {
					negativeLayers.add(id);
				} else {
					positiveLayers.add(id);
				}
			}
		}
	}

	public boolean isBaseMap(){
		return name != null && name.toLowerCase().contains(BASEMAP_NAME);
	}
}

src/net/osmand/osm/PoiCategory.java
public class PoiCategory extends PoiFilter {

	private List<PoiFilter> poiFilters = new ArrayList<PoiFilter>();
	private Set<PoiType> basemapPoi = null;
	private int regId;
	private String defaultTag;

	public PoiCategory(MapPoiTypes registry, String keyName, int regId) {
		super(registry, null, keyName);
		this.regId = regId;
	}

	public void addPoiType(PoiFilter poi) {
		poiFilters.add(poi);
	}

	public List<PoiFilter> getPoiFilters() {
		return poiFilters;
	}

	public String getDefaultTag() {
		if(defaultTag == null) {
			return keyName;
		}
		return defaultTag;
	}

	public void setDefaultTag(String defaultTag) {
		this.defaultTag = defaultTag;
	}

	public Map<PoiCategory, LinkedHashSet<String>> putTypes(
			Map<PoiCategory, LinkedHashSet<String>> acceptedTypes) {
		acceptedTypes.put(this, null);
		addReferenceTypes(acceptedTypes);
		return acceptedTypes;
	}

	public boolean isWiki() {
		return keyName.equals(MapPoiTypes.OSM_WIKI_CATEGORY);
	}

	public int ordinal() {
		return regId;
	}

	public void addBasemapPoi(PoiType pt) {
		if(basemapPoi == null) {
			basemapPoi = new HashSet<PoiType>();
		}
		basemapPoi.add(pt);
	}

	public boolean containsBasemapPoi(PoiType pt) {
		if(basemapPoi == null) {
			return false;
		}
		return basemapPoi.contains(pt);
	}
}

src/net/osmand/util/MapUtils.java
public class MapUtils {

    // TODO change the hostname back to osm.org once HTTPS works for it
    // https://github.com/openstreetmap/operations/issues/2
    private static final String BASE_SHORT_OSM_URL = "https://openstreetmap.org/go/";

	/**
     * This array is a lookup table that translates 6-bit positive integer
     * index values into their "Base64 Alphabet" equivalents as specified
     * in Table 1 of RFC 2045.
     */
    private static final char intToBase64[] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '~'
    };

	public static double getDistance(LatLon l, double latitude, double longitude){
		return getDistance(l.getLatitude(), l.getLongitude(), latitude, longitude);
	}

	private static double scalarMultiplication(double xA, double yA, double xB, double yB, double xC, double yC) {
		// Scalar multiplication between (AB, AC)
		return (xB - xA) * (xC - xA) + (yB- yA) * (yC -yA);
	}

	public static double getOrthogonalDistance(double lat, double lon, double fromLat, double fromLon, double toLat, double toLon) {
		return getDistance(getProjection(lat, lon, fromLat, fromLon, toLat, toLon), lat, lon);
	}

	public static double getOrthogonalDistance(LatLon latLon, LatLon fromLatLon, LatLon toLatLon){
		return getDistance(
				getProjection(latLon.getLatitude(), latLon.getLongitude(), fromLatLon.getLatitude(),
						fromLatLon.getLongitude(), toLatLon.getLatitude(), toLatLon.getLongitude()),
				latLon.getLatitude(), latLon.getLongitude());
	}

	public static LatLon getProjection(double lat, double lon, double fromLat, double fromLon, double toLat, double toLon) {
		// not very accurate computation on sphere but for distances < 1000m it is ok
		double mDist = (fromLat - toLat) * (fromLat - toLat) + (fromLon - toLon) * (fromLon - toLon);
		double projection = scalarMultiplication(fromLat, fromLon, toLat, toLon, lat, lon);
		double prlat;
		double prlon;
		if (projection < 0) {
			prlat = fromLat;
			prlon = fromLon;
		} else if (projection >= mDist) {
			prlat = toLat;
			prlon = toLon;
		} else {
			prlat = fromLat + (toLat - fromLat) * (projection / mDist);
			prlon = fromLon + (toLon - fromLon) * (projection / mDist);
		}
		return new LatLon(prlat, prlon);
	}

	public static double getProjectionCoeff(double lat, double lon, double fromLat, double fromLon, double toLat, double toLon) {
		// not very accurate computation on sphere but for distances < 1000m it is ok
		double mDist = (fromLat - toLat) * (fromLat - toLat) + (fromLon - toLon) * (fromLon - toLon);
		double projection = scalarMultiplication(fromLat, fromLon, toLat, toLon, lat, lon);
		double prlat;
		double prlon;
		if (projection < 0) {
			return 0;
		} else if (projection >= mDist) {
			return 1;
		} else {
			return (projection / mDist);
		}
	}

	private static double toRadians(double angdeg) {
//		return Math.toRadians(angdeg);
		return angdeg / 180.0 * Math.PI;
	}

	/**
	 * Gets distance in meters
	 */
	public static double getDistance(double lat1, double lon1, double lat2, double lon2){
		double R = 6372.8; // for haversine use R = 6372.8 km instead of 6371 km
		double dLat = toRadians(lat2-lat1);
		double dLon = toRadians(lon2-lon1);
		double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
		        Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
		        Math.sin(dLon/2) * Math.sin(dLon/2);
		//double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
		//return R * c * 1000;
		// simplyfy haversine:
		return (2 * R * 1000 * Math.asin(Math.sqrt(a)));
	}

	/**
	 * Gets distance in meters
	 */
	public static double getDistance(LatLon l1, LatLon l2){
		return getDistance(l1.getLatitude(), l1.getLongitude(), l2.getLatitude(), l2.getLongitude());
	}

	public static double checkLongitude(double longitude) {
		if(longitude > -180 && longitude <= 180) {
			return longitude;
		}
		while (longitude < -180 || longitude > 180) {
			if (longitude < 0) {
				longitude += 360;
			} else {
				longitude -= 360;
			}
		}
		return longitude;
	}

	public static double checkLatitude(double latitude) {
		if(latitude > -80 && latitude <= 80) {
			return latitude;
		}
		while (latitude < -90 || latitude > 90) {
			if (latitude < 0) {
				latitude += 180;
			} else {
				latitude -= 180;
			}
		}
		if(latitude < -85.0511) {
			return -85.0511;
 		} else if(latitude > 85.0511){
 			return 85.0511;
 		}
		return latitude;
	}

	public static int get31TileNumberX(double longitude){
		longitude = checkLongitude(longitude);
		long l = 1L << 31;
		return (int)((longitude + 180d)/360d * l);
	}
	public static int get31TileNumberY( double latitude){
		latitude = checkLatitude(latitude);
		double eval = Math.log( Math.tan(toRadians(latitude)) + 1/Math.cos(toRadians(latitude)) );
		long l = 1L << 31;
		if(eval > Math.PI){
			eval = Math.PI;
		}
		return  (int) ((1 - eval / Math.PI) / 2 * l);
	}

	public static double get31LongitudeX(int tileX){
		return MapUtils.getLongitudeFromTile(21, tileX /1024f);
	}

	public static double get31LatitudeY(int tileY){
		return MapUtils.getLatitudeFromTile(21, tileY / 1024f);
	}

	/**
	 *
	 * Theses methods operate with degrees (evaluating tiles & vice versa)
	 * degree longitude measurements (-180, 180) [27.56 Minsk]
	// degree latitude measurements (90, -90) [53.9]
	 */

	public static double getTileNumberX(float zoom, double longitude){
		longitude = checkLongitude(longitude);
		final double powZoom = getPowZoom(zoom);
		double dz = (longitude + 180d)/360d * powZoom;
		if (dz >= powZoom) {
			return powZoom - 0.01;
		}
		return dz;
	}

	public static double getTileNumberY(float zoom,  double latitude){
		latitude = checkLatitude(latitude);
		double eval = Math.log( Math.tan(toRadians(latitude)) + 1/Math.cos(toRadians(latitude)) );
		if (Double.isInfinite(eval) || Double.isNaN(eval)) {
			latitude = latitude < 0 ? - 89.9 : 89.9;
			eval = Math.log( Math.tan(toRadians(latitude)) + 1/Math.cos(toRadians(latitude)) );
		}
		return (1 - eval / Math.PI) / 2 * getPowZoom(zoom);
	}

	public static double getTileEllipsoidNumberY(float zoom, double latitude){
		final double E2 = (double) latitude * Math.PI / 180;
		final long sradiusa = 6378137;
		final long sradiusb = 6356752;
		final double J2 = (double) Math.sqrt(sradiusa * sradiusa - sradiusb * sradiusb)	/ sradiusa;
		final double M2 = (double) Math.log((1 + Math.sin(E2))
				/ (1 - Math.sin(E2)))/ 2- J2	* Math.log((1 + J2 * Math.sin(E2))/ (1 - J2 * Math.sin(E2))) / 2;
		final double B2 = getPowZoom(zoom);
		return B2 / 2 - M2 * B2 / 2 / Math.PI;
	}

	public static double getLatitudeFromEllipsoidTileY(float zoom, float tileNumberY){
		final double MerkElipsK = 0.0000001;
		final long sradiusa = 6378137;
		final long sradiusb = 6356752;
		final double FExct = (double) Math.sqrt(sradiusa * sradiusa
				- sradiusb * sradiusb)
				/ sradiusa;
		final double TilesAtZoom = getPowZoom(zoom);
		double result = (tileNumberY - TilesAtZoom / 2)
				/ -(TilesAtZoom / (2 * Math.PI));
		result = (2 * Math.atan(Math.exp(result)) - Math.PI / 2) * 180
				/ Math.PI;
		double Zu = result / (180 / Math.PI);
		double yy = (tileNumberY - TilesAtZoom / 2);

		double Zum1 = Zu;
		Zu = Math.asin(1 - ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct * Math.sin(Zum1), FExct))
				/ (Math.exp((2 * yy) / -(TilesAtZoom / (2 * Math.PI))) * Math.pow(1 + FExct * Math.sin(Zum1), FExct)));
		while (Math.abs(Zum1 - Zu) >= MerkElipsK) {
			Zum1 = Zu;
			Zu = Math.asin(1 - ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct * Math.sin(Zum1), FExct))
					/ (Math.exp((2 * yy) / -(TilesAtZoom / (2 * Math.PI))) * Math.pow(1 + FExct * Math.sin(Zum1), FExct)));
		}

		return Zu * 180 / Math.PI;
	}

	public static double getTileDistanceWidth(float zoom) {
		LatLon ll = new LatLon(30, MapUtils.getLongitudeFromTile(zoom, 0));
		LatLon ll2 = new LatLon(30, MapUtils.getLongitudeFromTile(zoom, 1));
		return getDistance(ll, ll2) ;
	}

	public static double getLongitudeFromTile(double zoom, double x) {
		return x / getPowZoom(zoom) * 360.0 - 180.0;
	}

	public static double getPowZoom(double zoom){
		if(zoom >= 0 && zoom - Math.floor(zoom) < 0.001f){
			return 1 << ((int)zoom);
		} else {
			return Math.pow(2, zoom);
		}
	}

	public static float calcDiffPixelX(float rotateSin, float rotateCos, float dTileX, float dTileY, float tileSize){
		return (rotateCos * dTileX - rotateSin * dTileY) * tileSize ;
	}

	public static float calcDiffPixelY(float rotateSin, float rotateCos, float dTileX, float dTileY, float tileSize){
		return (rotateSin * dTileX + rotateCos * dTileY) * tileSize ;
	}

	public static double getLatitudeFromTile(float zoom, double y) {
		int sign = y < 0 ? -1 : 1;
		return Math.atan(sign * Math.sinh(Math.PI * (1 - 2 * y / getPowZoom(zoom)))) * 180d / Math.PI;
	}

	public static int getPixelShiftX(float zoom, double long1, double long2, double tileSize){
		return (int) ((getTileNumberX(zoom, long1) - getTileNumberX(zoom, long2)) * tileSize);
	}

	public static int getPixelShiftY(float zoom, double lat1, double lat2, double tileSize){
		return (int) ((getTileNumberY(zoom, lat1) - getTileNumberY(zoom, lat2)) * tileSize);
	}

	public static void sortListOfMapObject(List<? extends MapObject> list, final double lat, final double lon){
		Collections.sort(list, new Comparator<MapObject>() {
			@Override
			public int compare(MapObject o1, MapObject o2) {
				return Double.compare(MapUtils.getDistance(o1.getLocation(), lat, lon), MapUtils.getDistance(o2.getLocation(),
						lat, lon));
			}
		});
	}

	public static String buildGeoUrl(double latitude, double longitude, int zoom) {
        return "geo:" + ((float) latitude) + "," + ((float)longitude) + "?z=" + zoom;
	}

	// Examples
//	System.out.println(buildShortOsmUrl(51.51829d, 0.07347d, 16)); // http://osm.org/go/0EEQsyfu
//	System.out.println(buildShortOsmUrl(52.30103d, 4.862927d, 18)); // http://osm.org/go/0E4_JiVhs
//	System.out.println(buildShortOsmUrl(40.59d, -115.213d, 9)); // http://osm.org/go/TelHTB--
	public static String buildShortOsmUrl(double latitude, double longitude, int zoom){
        return BASE_SHORT_OSM_URL + createShortLinkString(latitude, longitude, zoom) + "?m";
	}

	public static String createShortLinkString(double latitude, double longitude, int zoom) {
		long lat = (long) (((latitude + 90d)/180d)*(1L << 32));
		long lon = (long) (((longitude + 180d)/360d)*(1L << 32));
		long code = interleaveBits(lon, lat);
		String str = "";
	    // add eight to the zoom level, which approximates an accuracy of one pixel in a tile.
		for (int i = 0; i < Math.ceil((zoom + 8) / 3d); i++) {
		    str += intToBase64[(int) ((code >> (58 - 6 * i)) & 0x3f)];
		}
		// append characters onto the end of the string to represent
		// partial zoom levels (characters themselves have a granularity of 3 zoom levels).
		for (int j = 0; j < (zoom + 8) % 3; j++) {
			str += '-';
		}
		return str;
	}

	public static GeoParsedPoint decodeShortLinkString(String s) {
		// convert old shortlink format to current one
		s = s.replaceAll("@", "~");
		int i = 0;
		long x = 0;
		long y = 0;
		int z = -8;

		for (i = 0; i < s.length(); i++) {
			int digit = -1;
			char c = s.charAt(i);
			for (int j = 0; j < intToBase64.length; j++)
				if (c == intToBase64[j]) {
					digit = j;
					break;
				}
			if (digit < 0)
				break;
			if (digit < 0)
				break;
			// distribute 6 bits into x and y
			x <<= 3;
			y <<= 3;
			for (int j = 2; j >= 0; j--) {
				x |= ((digit & (1 << (j+j+1))) == 0 ? 0 : (1 << j));
				y |= ((digit & (1 << (j+j))) == 0 ? 0 : (1 << j));
			}
			z += 3;
		}
		double lon = x * Math.pow(2, 2 - 3 * i) * 90. - 180;
		double lat = y * Math.pow(2, 2 - 3 * i) * 45. - 90;
		// adjust z
		if(i < s.length() && s.charAt(i) == '-') {
			z -= 2;
			if(i + 1 < s.length() && s.charAt(i + 1) == '-')
				z++;
		}
		return new GeoParsedPoint(lat, lon, z);
	}

	/**
	 * interleaves the bits of two 32-bit numbers. the result is known as a Morton code.
	 */
	private static long interleaveBits(long x, long y){
		long c = 0;
		for(byte b = 31; b>=0; b--){
			c = (c << 1) | ((x >> b) & 1);
			c = (c << 1) | ((y >> b) & 1);
		}
		return c;
	}

	/**
	 * Calculate rotation diff D, that R (rotate) + D = T (targetRotate)
	 * D is between -180, 180
	 * @param rotate
	 * @param targetRotate
	 * @return
	 */
	public static float unifyRotationDiff(float rotate, float targetRotate) {
		float d = targetRotate - rotate;
		while(d >= 180){
			d -= 360;
		}
		while(d < -180){
			d += 360;
		}
		return d;
	}

	/**
	 * Calculate rotation diff D, that R (rotate) + D = T (targetRotate)
	 * D is between -180, 180
	 * @param rotate
	 * @return
	 */
	public static float unifyRotationTo360(float rotate) {
		while(rotate < -180){
			rotate += 360;
		}
		while(rotate > +180){
			rotate -= 360;
		}
		return rotate;
	}

	/**
	 * @param diff align difference between 2 angles ]-PI, PI]
	 * @return
	 */
	public static double alignAngleDifference(double diff) {
		while(diff > Math.PI) {
			diff -= 2 * Math.PI;
		}
		while(diff <=-Math.PI) {
			diff += 2 * Math.PI;
		}
		return diff;

	}

	/**
	 * diff align difference between 2 angles ]-180, 180]
	 * @return
	 */
	public static double degreesDiff(double a1, double a2) {
		double diff = a1 - a2;
		while(diff > 180) {
			diff -= 360;
		}
		while(diff <=-180) {
			diff += 360;
		}
		return diff;

	}

	public static double convert31YToMeters(double y1, double y2) {
		// translate into meters
		return (y1 - y2) * 0.01863d;
	}

	public static double convert31XToMeters(double x1, double x2) {
		// translate into meters
		return (x1 - x2) * 0.011d;
	}

	public static QuadPoint getProjectionPoint31(int px, int py, int st31x, int st31y,int end31x, int end31y) {
		double projection = calculateProjection31TileMetric(st31x, st31y, end31x,
				end31y, px, py);
		double mDist = squareRootDist31(end31x, end31y, st31x,
				st31y);
		int pry = end31y;
		int prx = end31x;
		if (projection < 0) {
			prx = st31x;
			pry = st31y;
		} else if (projection >= mDist * mDist) {
			prx = end31x;
			pry = end31y;
		} else {
			prx = (int) (st31x + (end31x - st31x)
					* (projection / (mDist * mDist)));
			pry = (int) (st31y + (end31y - st31y)
					* (projection / (mDist * mDist)));
		}
		return new QuadPoint(prx, pry);
	}

	public static double squareRootDist31(int x1, int y1, int x2, int y2) {
		// translate into meters
		double dy = MapUtils.convert31YToMeters(y1, y2);
		double dx = MapUtils.convert31XToMeters(x1, x2);
		return Math.sqrt(dx * dx + dy * dy);
//		return measuredDist(x1, y1, x2, y2);
	}

	public static double measuredDist31(int x1, int y1, int x2, int y2) {
		return getDistance(MapUtils.get31LatitudeY(y1), MapUtils.get31LongitudeX(x1), MapUtils.get31LatitudeY(y2), MapUtils.get31LongitudeX(x2));
	}

	public static double squareDist31TileMetric(int x1, int y1, int x2, int y2) {
		// translate into meters
		double dy = convert31YToMeters(y1, y2);
		double dx = convert31XToMeters(x1, x2);
		return dx * dx + dy * dy;
	}

	public static double calculateProjection31TileMetric(int xA, int yA, int xB, int yB, int xC, int yC) {
		// Scalar multiplication between (AB, AC)
		double multiple = MapUtils.convert31XToMeters(xB, xA) * MapUtils.convert31XToMeters(xC, xA) +
				MapUtils.convert31YToMeters(yB, yA) * MapUtils.convert31YToMeters(yC, yA);
		return multiple;
	}

	public static boolean rightSide(double lat, double lon,
												  double aLat, double aLon,
												  double bLat, double bLon) {
		double ax = aLon - lon;
		double ay = aLat - lat;
		double bx = bLon - lon;
		double by = bLat - lat;
		double sa = ax * by - bx * ay;
		return sa < 0;
	}

}

src/net/osmand/binary/BinaryMapIndexReader.java
public static interface SearchPoiTypeFilter {

	public boolean accept(PoiCategory type, String subcategory);

	public boolean isEmpty();
}

src/net/osmand/binary/BinaryMapIndexReader.java
public static class MapObjectStat {
	public int lastStringNamesSize;
	public int lastObjectIdSize;
	public int lastObjectHeaderInfo;
	public int lastObjectAdditionalTypes;
	public int lastObjectTypes;
	public int lastObjectCoordinates;

	public int lastObjectSize ;
	public int lastBlockStringTableSize;
	public int lastBlockHeaderInfo;

	public void addBlockHeader(int typesFieldNumber, int sizeL) {
		lastBlockHeaderInfo +=
				CodedOutputStream.computeTagSize(typesFieldNumber) +
				CodedOutputStream.computeRawVarint32Size(sizeL);
	}

	public void addTagHeader(int typesFieldNumber, int sizeL) {
		lastObjectHeaderInfo +=
				CodedOutputStream.computeTagSize(typesFieldNumber) +
				CodedOutputStream.computeRawVarint32Size(sizeL);
	}

	public void clearObjectStats() {
		lastStringNamesSize = 0;
		lastObjectIdSize = 0;
		lastObjectHeaderInfo = 0;
		lastObjectAdditionalTypes = 0;
		lastObjectTypes = 0;
		lastObjectCoordinates = 0;
	}
}

src/com/google/protobuf/WireFormat.java
public final class WireFormat {
  // Do not allow instantiation.
  private WireFormat() {}

  static final int WIRETYPE_VARINT           = 0;
  static final int WIRETYPE_FIXED64          = 1;
  static final int WIRETYPE_LENGTH_DELIMITED = 2;
  static final int WIRETYPE_START_GROUP      = 3;
  static final int WIRETYPE_END_GROUP        = 4;
  static final int WIRETYPE_FIXED32          = 5;
  // Osmand Delta change
  public static final int WIRETYPE_FIXED32_LENGTH_DELIMITED = 6;

  static final int TAG_TYPE_BITS = 3;
  static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1;

  /** Given a tag value, determines the wire type (the lower 3 bits). */
  public static int getTagWireType(final int tag) {
    return tag & TAG_TYPE_MASK;
  }

  /** Given a tag value, determines the field number (the upper 29 bits). */
  public static int getTagFieldNumber(final int tag) {
    return tag >>> TAG_TYPE_BITS;
  }

  /** Makes a tag value given a field number and wire type. */
  static int makeTag(final int fieldNumber, final int wireType) {
    return (fieldNumber << TAG_TYPE_BITS) | wireType;
  }

  /**
   * Lite equivalent to {@link Descriptors.FieldDescriptor.JavaType}.  This is
   * only here to support the lite runtime and should not be used by users.
   */
  public enum JavaType {
    INT(0),
    LONG(0L),
    FLOAT(0F),
    DOUBLE(0D),
    BOOLEAN(false),
    STRING(""),
    BYTE_STRING(ByteString.EMPTY),
    ENUM(null),
    MESSAGE(null);

    JavaType(final Object defaultDefault) {
      this.defaultDefault = defaultDefault;
    }

    /**
     * The default default value for fields of this type, if it's a primitive
     * type.
     */
    Object getDefaultDefault() {
      return defaultDefault;
    }

    private final Object defaultDefault;
  }

  /**
   * Lite equivalent to {@link Descriptors.FieldDescriptor.Type}.  This is
   * only here to support the lite runtime and should not be used by users.
   */
  public enum FieldType {
    DOUBLE  (JavaType.DOUBLE     , WIRETYPE_FIXED64         ),
    FLOAT   (JavaType.FLOAT      , WIRETYPE_FIXED32         ),
    INT64   (JavaType.LONG       , WIRETYPE_VARINT          ),
    UINT64  (JavaType.LONG       , WIRETYPE_VARINT          ),
    INT32   (JavaType.INT        , WIRETYPE_VARINT          ),
    FIXED64 (JavaType.LONG       , WIRETYPE_FIXED64         ),
    FIXED32 (JavaType.INT        , WIRETYPE_FIXED32         ),
    BOOL    (JavaType.BOOLEAN    , WIRETYPE_VARINT          ),
    STRING  (JavaType.STRING     , WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    GROUP   (JavaType.MESSAGE    , WIRETYPE_START_GROUP     ) {
      public boolean isPackable() { return false; }
    },
    MESSAGE (JavaType.MESSAGE    , WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    BYTES   (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) {
      public boolean isPackable() { return false; }
    },
    UINT32  (JavaType.INT        , WIRETYPE_VARINT          ),
    ENUM    (JavaType.ENUM       , WIRETYPE_VARINT          ),
    SFIXED32(JavaType.INT        , WIRETYPE_FIXED32         ),
    SFIXED64(JavaType.LONG       , WIRETYPE_FIXED64         ),
    SINT32  (JavaType.INT        , WIRETYPE_VARINT          ),
    SINT64  (JavaType.LONG       , WIRETYPE_VARINT          );

    FieldType(final JavaType javaType, final int wireType) {
      this.javaType = javaType;
      this.wireType = wireType;
    }

    private final JavaType javaType;
    private final int wireType;

    public JavaType getJavaType() { return javaType; }
    public int getWireType() { return wireType; }

    public boolean isPackable() { return true; }
  }

  // Field numbers for feilds in MessageSet wire format.
  static final int MESSAGE_SET_ITEM    = 1;
  static final int MESSAGE_SET_TYPE_ID = 2;
  static final int MESSAGE_SET_MESSAGE = 3;

  // Tag numbers.
  static final int MESSAGE_SET_ITEM_TAG =
    makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP);
  static final int MESSAGE_SET_ITEM_END_TAG =
    makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP);
  static final int MESSAGE_SET_TYPE_ID_TAG =
    makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT);
  static final int MESSAGE_SET_MESSAGE_TAG =
    makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED);
}

Answer: for (CitiesBlock c : cities) {

<think>
The code is inside the printFileInformation method handling an AddressRegion. After retrieving the list of city blocks, the next logical step is to iterate over them to print details. The typical pattern in this method is a for‑each loop over the collection, so the next line should start that loop: "for (CitiesBlock cb : cities) {".
</think>
for (CitiesBlock cb : cities) {