All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.llbit.chunky.map.WorldMapLoader Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/* Copyright (c) 2010-2016 Jesper Öqvist 
 *
 * This file is part of Chunky.
 *
 * Chunky is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chunky is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Chunky.  If not, see .
 */
package se.llbit.chunky.map;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
import se.llbit.chunky.PersistentSettings;
import se.llbit.chunky.main.ZipExportJob;
import se.llbit.chunky.renderer.ChunkViewListener;
import se.llbit.chunky.ui.ChunkyFxController;
import se.llbit.chunky.ui.MapViewMode;
import se.llbit.chunky.ui.ProgressTracker;
import se.llbit.chunky.world.Block;
import se.llbit.chunky.world.Chunk;
import se.llbit.chunky.world.ChunkPosition;
import se.llbit.chunky.world.ChunkSelectionTracker;
import se.llbit.chunky.world.ChunkTopographyUpdater;
import se.llbit.chunky.world.ChunkView;
import se.llbit.chunky.world.DeleteChunksJob;
import se.llbit.chunky.world.EmptyWorld;
import se.llbit.chunky.world.RegionChangeMonitor;
import se.llbit.chunky.world.RegionParser;
import se.llbit.chunky.world.RegionQueue;
import se.llbit.chunky.world.World;
import se.llbit.chunky.world.listeners.ChunkTopographyListener;
import se.llbit.math.Vector3;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class WorldMapLoader implements ChunkTopographyListener {
  private final ChunkyFxController controller;

  private BooleanProperty trackPlayer =
      new SimpleBooleanProperty(PersistentSettings.getFollowPlayer());
  private BooleanProperty trackCamera =
      new SimpleBooleanProperty(PersistentSettings.getFollowCamera());

  private World world = EmptyWorld.instance;

  private final RegionQueue regionQueue = new RegionQueue();

  private final ChunkTopographyUpdater topographyUpdater = new ChunkTopographyUpdater();
  private final RegionChangeMonitor refresher = new RegionChangeMonitor(this);

  private int currentDimension = PersistentSettings.getDimension();
  protected ChunkSelectionTracker chunkSelection = new ChunkSelectionTracker();

  private BooleanProperty highlightEnabled = new SimpleBooleanProperty(false);
  private Block highlightBlock = Block.get(Block.DIAMONDORE_ID);
  private Color highlightColor = Color.CRIMSON;

  private volatile ObjectProperty map = new SimpleObjectProperty<>(ChunkView.EMPTY);
  private volatile ChunkView minimap = ChunkView.EMPTY;

  int minimapWidth = 100;
  int minimapHeight = 100;

  private List viewListeners = new ArrayList<>();
  private List worldLoadListeners = new ArrayList<>();

  public WorldMapLoader(ChunkyFxController controller) {
    this.controller = controller;

    map.addListener((observable, oldValue, newValue) -> {
      if (oldValue.layer != newValue.layer) {
        world.setCurrentLayer(newValue.layer);
        for (ChunkViewListener listener : viewListeners) {
          listener.layerChanged(newValue.layer);
        }
      }
      if (newValue.shouldRepaint(oldValue)) {
        viewUpdated(newValue);
      }
    });

    trackPlayer.addListener(e -> {
      boolean track = trackPlayer.get();
      PersistentSettings.setFollowPlayer(track);
      if (track) {
        panToPlayer();
      }
    });

    trackCamera.addListener(e -> {
      boolean track = trackCamera.get();
      PersistentSettings.setFollowCamera(track);
      if (track) {
        controller.panToCamera();
      }
    });

    highlightEnabled.addListener(e -> {
      setRenderer(MapViewMode.LAYER);
      notifyViewUpdated();
    });

    // Start worker threads.
    RegionParser[] regionParsers = new RegionParser[3];
    for (int i = 0; i < regionParsers.length; ++i) {
      regionParsers[i] = new RegionParser(this, regionQueue);
      regionParsers[i].start();
    }
    topographyUpdater.start();
    refresher.start();
  }

  public void loadWorld(File worldDir) {
    if (worldDir != null && World.isWorldDir(worldDir)) {
      loadWorld(new World(worldDir, false));
    }
  }

  /**
   * This is called when a new world is loaded, and when switching to a
   * different dimension.
   *
   * 

NB: When switching dimension this is called with newWorld = world. */ public synchronized void loadWorld(World newWorld) { // Dispose old world. world.dispose(); newWorld.reload(); newWorld.addChunkUpdateListener(controller); newWorld.addChunkUpdateListener(controller.getMap()); newWorld.addChunkUpdateListener(controller.getMinimap()); chunkSelection.clearSelection(); world = newWorld; world.addChunkDeletionListener(chunkSelection); world.addChunkTopographyListener(this); // Dimension must be set before chunks are loaded. world.setDimension(currentDimension); Vector3 playerPos = world.playerPos(); if (playerPos != null) { panToPlayer(); } else { panTo(0, 0); } File newWorldDir = world.getWorldDirectory(); if (newWorldDir != null) { PersistentSettings.setLastWorld(newWorldDir); } worldLoadListeners.forEach(Runnable::run); } public void addWorldLoadListener(Runnable callback) { worldLoadListeners.add(callback); } /** * Called when the map view has changed. */ public synchronized void viewUpdated(ChunkView mapView) { refresher.setView(mapView); minimap = new ChunkView(mapView.x, mapView.z, minimapWidth, minimapHeight, 1, MapViewMode.BIOMES, mapView.layer); int rx0 = Math.min(minimap.prx0, mapView.prx0); int rx1 = Math.max(minimap.prx1, mapView.prx1); int rz0 = Math.min(minimap.prz0, mapView.prz0); int rz1 = Math.max(minimap.prz1, mapView.prz1); // Enqueue visible regions and chunks. for (int rx = rx0; rx <= rx1; ++rx) { for (int rz = rz0; rz <= rz1; ++rz) { regionQueue.add(ChunkPosition.get(rx, rz)); } } notifyViewUpdated(); } /** * Called when the map has been resized. */ public void setMapSize(int width, int height) { ChunkView mapView = map.get(); if (width != mapView.width || height != mapView.height) { map.set(new ChunkView(mapView.x, mapView.z, width, height, mapView.scale, mapView.renderer, mapView.layer)); } } /** * Called when the minimap has been resized. */ public void setMinimapSize(int width, int height) { if (width != minimapWidth || height != minimapHeight) { minimapWidth = width; minimapHeight = height; viewUpdated(map.get()); } } /** * Move the map view. */ public synchronized void moveView(double dx, double dz) { ChunkView mapView = map.get(); panTo(mapView.x + dx, mapView.z + dz); viewUpdated(map.get()); } /** Set the map view by block coordinates. */ public void panTo(Vector3 pos) { // Convert from block coordinates to chunk coordinates. panTo(pos.x / 16, pos.z / 16); } /** * Move the map view. */ public synchronized void panTo(double x, double z) { ChunkView mapView = map.get(); map.set(new ChunkView(x, z, mapView.width, mapView.height, mapView.scale, mapView.renderer, mapView.layer)); } /** * Set the currently viewed layer. */ public synchronized void setLayer(int layer) { ChunkView mapView = map.get(); map.set(new ChunkView(mapView.x, mapView.z, mapView.width, mapView.height, mapView.scale, mapView.renderer, layer)); } /** * Select specific chunk. */ public synchronized void selectChunk(int cx, int cz) { chunkSelection.selectChunk(world, cx, cz); } /** * @return The current world */ public World getWorld() { return world; } /** * @return The name of the current world */ public String getWorldName() { return world.levelName(); } /** * @return The current map view */ public ChunkView getMapView() { return map.get(); } /** * @return The map view property. */ public ObjectProperty getMapViewProperty() { return map; } /** * @return The current minimap view */ public ChunkView getMinimapView() { return minimap; } /** * @return The current map renderer */ public MapViewMode getChunkRenderer() { return map.get().renderer; } /** * @return The chunk selection tracker */ public ChunkSelectionTracker getChunkSelection() { return chunkSelection; } public void panToPlayer() { Vector3 pos = world.playerPos(); if (pos != null) { panTo(pos); } } public void addViewListener(ChunkViewListener listener) { viewListeners.add(listener); } /** * The region was changed. */ public void regionUpdated(ChunkPosition region) { regionQueue.add(region); } public synchronized Vector3 getPosition() { ChunkView mapView = map.get(); return new Vector3(mapView.x, world.currentLayer(), mapView.z); } /** * @return The currently viewed layer */ public synchronized int getLayer() { return world.currentLayer(); } @Override public void chunksTopographyUpdated(Chunk chunk) { topographyUpdater.addChunk(chunk); } /** * Flush all cached chunks and regions, forcing them to be reloaded * for the current world. */ public synchronized void reloadWorld() { world.reload(); notifyViewUpdated(); } /** * Set the current map renderer. */ public synchronized void setRenderer(MapViewMode renderer) { ChunkView mapView = map.get(); if (renderer != mapView.renderer) { map.set(new ChunkView(mapView.x, mapView.z, mapView.width, mapView.height, mapView.scale, renderer, mapView.layer)); } } /** * Toggle chunk selection. */ public synchronized void toggleChunkSelection(int cx, int cz) { chunkSelection.toggleChunk(world, cx, cz); } /** * Set the current dimension. * * @param value Must be a valid dimension index */ public void setDimension(int value) { if (value != currentDimension) { currentDimension = value; PersistentSettings.setDimension(currentDimension); // Note: here we are loading the same world again just to load the // next dimension. However, this is a very ugly way to handle dimension // switching and it would probably be much better to just create a fresh // world instance to load. loadWorld(world); } } /** * @return true if chunks or regions are currently being parsed */ public boolean isLoading() { return !regionQueue.isEmpty(); } /** * Clears the chunk selection. */ public synchronized void clearChunkSelection() { chunkSelection.clearSelection(); } /** * @return The current block scale of the map view */ public int getScale() { return map.get().scale; } /** * Called when the map view has been dragged by the user. */ public void viewDragged(int dx, int dy) { double scale = getScale(); moveView(dx / scale, dy / scale); viewListeners.forEach(ChunkViewListener::viewMoved); } /** * Select chunks within a rectangle. */ public void selectChunks(int cx0, int cx1, int cz0, int cz1) { chunkSelection.selectChunks(world, cx0, cz0, cx1, cz1); } /** * Deselect chunks within a rectangle. */ public void deselectChunks(int cx0, int cx1, int cz0, int cz1) { chunkSelection.deselectChunks(cx0, cz0, cx1, cz1); } /** * Delete the currently selected chunks from the current world. */ public void deleteSelectedChunks(ProgressTracker progress) { Collection selected = chunkSelection.getSelection(); if (!selected.isEmpty() && !progress.isBusy()) { DeleteChunksJob job = new DeleteChunksJob(world, selected, progress); job.start(); } } /** * Export the selected chunks to a zip file. */ public synchronized void exportZip(File targetFile, ProgressTracker progress) { new ZipExportJob(world, chunkSelection.getSelection(), targetFile, progress).start(); } /** * Select the region containing the given chunk. */ public void selectRegion(int cx, int cz) { chunkSelection.selectRegion(world, cx, cz); } /** * Modify the block scale of the map view. */ public synchronized void setScale(int blockScale) { ChunkView mapView = map.get(); blockScale = ChunkView.clampScale(blockScale); if (blockScale != mapView.scale) { map.set(new ChunkView(mapView.x, mapView.z, mapView.width, mapView.height, blockScale, mapView.renderer, mapView.layer)); } } /** * @return The current highlight color */ public Color highlightColor() { return highlightColor; } /** * @return The currently highlighted block type */ public Block highlightBlock() { return highlightBlock; } public BooleanProperty highlightEnabledProperty() { return highlightEnabled; } public boolean highlightEnabled() { return highlightEnabled.get(); } /** * Set a new block type to highlight. */ public void highlightBlock(Block hlBlock) { this.highlightBlock = hlBlock; if (highlightEnabled.get()) { // TODO: make separate highlight update event. notifyViewUpdated(); } } /** * Set a new highlight color. */ public void highlightColor(Color newColor) { highlightColor = newColor; if (highlightEnabled.get()) { // TODO: make separate highlight update event. notifyViewUpdated(); } } /** * Notify view listeners that the view has changed. */ private void notifyViewUpdated() { viewListeners.forEach(ChunkViewListener::viewUpdated); } public int getDimension() { return currentDimension; } public BooleanProperty trackPlayerProperty() { return trackPlayer; } public BooleanProperty trackCameraProperty() { return trackCamera; } public void drawCameraVisualization() { viewListeners.forEach(ChunkViewListener::cameraPositionUpdated); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy