com.github.begla.blockmania.world.main.World.java Source code

Java tutorial

Introduction

Here is the source code for com.github.begla.blockmania.world.main.World.java

Source

/*
 * Copyright 2011 Benjamin Glatzel <benjamin.glatzel@me.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.begla.blockmania.world.main;

import com.github.begla.blockmania.audio.AudioManager;
import com.github.begla.blockmania.configuration.ConfigurationManager;
import com.github.begla.blockmania.game.Blockmania;
import com.github.begla.blockmania.game.PortalManager;
import com.github.begla.blockmania.game.blueprints.BlockGrid;
import com.github.begla.blockmania.game.mobs.MobManager;
import com.github.begla.blockmania.generators.ChunkGeneratorTerrain;
import com.github.begla.blockmania.rendering.interfaces.RenderableObject;
import com.github.begla.blockmania.rendering.manager.ShaderManager;
import com.github.begla.blockmania.rendering.manager.TextureManager;
import com.github.begla.blockmania.rendering.particles.BlockParticleEmitter;
import com.github.begla.blockmania.world.characters.Player;
import com.github.begla.blockmania.world.chunk.Chunk;
import com.github.begla.blockmania.world.chunk.ChunkMesh;
import com.github.begla.blockmania.world.chunk.ChunkUpdateManager;
import com.github.begla.blockmania.world.entity.Entity;
import com.github.begla.blockmania.world.horizon.Skysphere;
import com.github.begla.blockmania.world.physics.BulletPhysicsRenderer;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.newdawn.slick.openal.SoundStore;

import javax.vecmath.Vector3f;
import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Level;

import static org.lwjgl.opengl.GL11.glColorMask;

/**
 * The world of Blockmania. At its most basic the world contains chunks (consisting of a fixed amount of blocks)
 * and the player.
 * <p/>
 * The world is randomly generated by using a bunch of Perlin noise generators initialized
 * with a favored seed value.
 *
 * @author Benjamin Glatzel <benjamin.glatzel@me.com>
 */
public final class World implements RenderableObject {

    private static final int MAX_CHUNK_UPDATES_PER_ITERATION = (Integer) ConfigurationManager.getInstance()
            .getConfig().get("System.maxChunkUpdatesPerIteration");

    /* VIEWING DISTANCE */
    private int _viewingDistance = 8;

    /* WORLD PROVIDER */
    private WorldProvider _worldProvider;

    /* PLAYER */
    private Player _player;

    /* CHUNKS */
    private ArrayList<Chunk> _chunksInProximity = new ArrayList<Chunk>(), _visibleChunks = new ArrayList<Chunk>();

    /* CORE GAME OBJECTS */
    private PortalManager _portalManager;
    private MobManager _mobManager;

    /* PARTICLE EMITTERS */
    private final BlockParticleEmitter _blockParticleEmitter = new BlockParticleEmitter(this);

    /* HORIZON */
    private final Skysphere _skysphere;

    /* WATER AND LAVA ANIMATION */
    private int _tick = 0;
    private int _tickTock = 0;
    private long _lastTick;

    /* UPDATING */
    private final ChunkUpdateManager _chunkUpdateManager;

    /* EVENTS */
    private final WorldTimeEventManager _worldTimeEventManager;

    /* PHYSICS */
    private BulletPhysicsRenderer _bulletPhysicsRenderer;

    /* BLOCK GRID */
    private final BlockGrid _blockGrid;

    /* SIMULATION */
    private long _lastSimulationStep = Blockmania.getInstance().getTime();

    /**
     * Initializes a new (local) world for the single player mode.
     *
     * @param title The title/description of the world
     * @param seed  The seed string used to generate the terrain
     */
    public World(String title, String seed) {
        _worldProvider = new LocalWorldProvider(title, seed);
        _skysphere = new Skysphere(this);
        _chunkUpdateManager = new ChunkUpdateManager();
        _worldTimeEventManager = new WorldTimeEventManager(_worldProvider);
        _portalManager = new PortalManager(this);
        _mobManager = new MobManager(this);
        _blockGrid = new BlockGrid(this);
        _bulletPhysicsRenderer = new BulletPhysicsRenderer(this);

        createMusicTimeEvents();
    }

    /**
     * Updates the list of chunks around the player.
     *
     * @return True if the list was changed
     */
    private boolean updateChunksInProximity() {
        _chunksInProximity.clear();

        for (int x = -(_viewingDistance / 2); x < (_viewingDistance / 2); x++) {
            for (int z = -(_viewingDistance / 2); z < (_viewingDistance / 2); z++) {
                Chunk c = _worldProvider.getChunkProvider().loadOrCreateChunk(calcPlayerChunkOffsetX() + x,
                        calcPlayerChunkOffsetZ() + z);
                _chunksInProximity.add(c);
            }
        }

        Collections.sort(_chunksInProximity);
        return true;
    }

    public boolean isInRange(Vector3f pos) {
        Vector3f dist = new Vector3f();
        dist.sub(_player.getPosition(), pos);

        float distLength = dist.length();

        return distLength < (_viewingDistance * 8);
    }

    /**
     * Creates the world time events to play the game's soundtrack at specific times.
     */
    public void createMusicTimeEvents() {
        // SUNRISE
        _worldTimeEventManager.addWorldTimeEvent(new WorldTimeEvent(0.01, true) {
            @Override
            public void run() {
                SoundStore.get().setMusicVolume(0.2f);
                AudioManager.getInstance().getAudio("Sunrise").playAsMusic(1.0f, 1.0f, false);
            }
        });

        // AFTERNOON
        _worldTimeEventManager.addWorldTimeEvent(new WorldTimeEvent(0.33, true) {
            @Override
            public void run() {
                SoundStore.get().setMusicVolume(0.2f);
                AudioManager.getInstance().getAudio("Afternoon").playAsMusic(1.0f, 1.0f, false);
            }
        });

        // SUNSET
        _worldTimeEventManager.addWorldTimeEvent(new WorldTimeEvent(0.44, true) {
            @Override
            public void run() {
                SoundStore.get().setMusicVolume(0.2f);
                AudioManager.getInstance().getAudio("Sunset").playAsMusic(1.0f, 1.0f, false);
            }
        });
    }

    /**
     * Fetches the currently visible chunks (in sight of the player).
     *
     * @return The visible chunks
     */
    public void updateVisibleChunks() {
        _visibleChunks.clear();

        int updateCounter = 0;
        for (int i = 0; i < _chunksInProximity.size(); i++) {
            Chunk c = _chunksInProximity.get(i);

            if (isChunkVisible(c)) {
                c.setVisible(true);
                _visibleChunks.add(c);

                if (c.isDirty() || c.isLightDirty()) {
                    if (_chunkUpdateManager.queueChunkUpdate(c, ChunkUpdateManager.UPDATE_TYPE.DEFAULT)) {
                        continue;
                    }

                    continue;
                }

                if (updateCounter < MAX_CHUNK_UPDATES_PER_ITERATION) {
                    if (c.generateVBOs()) {
                        updateCounter++;
                    }

                    c.update();
                }

                continue;
            }

            c.setVisible(false);
        }
    }

    /**
     * Renders the world.
     */
    public void render() {
        /* SKYSPHERE */
        _player.getActiveCamera().lookThroughNormalized();
        _skysphere.render();

        /* WORLD RENDERING */
        _player.getActiveCamera().lookThrough();

        _player.render();
        renderChunksAndEntities();

        /* PARTICLE EFFECTS */
        _blockParticleEmitter.render();
        _blockGrid.render();
    }

    /**
     * Renders all chunks that are currently in the player's field of view.
     */
    private void renderChunksAndEntities() {

        ShaderManager.getInstance().enableShader("chunk");

        GL13.glActiveTexture(GL13.GL_TEXTURE1);
        TextureManager.getInstance().bindTexture("custom_lava_still");
        GL13.glActiveTexture(GL13.GL_TEXTURE2);
        TextureManager.getInstance().bindTexture("custom_water_still");
        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        TextureManager.getInstance().bindTexture("terrain");

        int daylight = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "daylight");
        int swimming = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "swimming");

        int lavaTexture = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "textureLava");
        int waterTexture = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"),
                "textureWater");
        int textureAtlas = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"),
                "textureAtlas");
        GL20.glUniform1i(lavaTexture, 1);
        GL20.glUniform1i(waterTexture, 2);
        GL20.glUniform1i(textureAtlas, 0);

        int tick = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "tick");

        GL20.glUniform1f(tick, _tick);
        GL20.glUniform1f(daylight, getDaylight());
        GL20.glUniform1i(swimming, _player.isHeadUnderWater() ? 1 : 0);

        // OPAQUE ELEMENTS
        for (int i = 0; i < _visibleChunks.size(); i++) {
            Chunk c = _visibleChunks.get(i);

            c.render(ChunkMesh.RENDER_TYPE.OPAQUE);
            c.render(ChunkMesh.RENDER_TYPE.LAVA);

            if ((Boolean) ConfigurationManager.getInstance().getConfig().get("System.Debug.chunkOutlines")) {
                c.getAABB().render();
            }
        }

        for (int i = 0; i < _visibleChunks.size(); i++) {
            Chunk c = _visibleChunks.get(i);

            c.render(ChunkMesh.RENDER_TYPE.BILLBOARD_AND_TRANSLUCENT);
        }

        for (int j = 0; j < 2; j++) {
            // ANIMATED WATER
            for (int i = 0; i < _visibleChunks.size(); i++) {
                Chunk c = _visibleChunks.get(i);

                if (j == 0) {
                    glColorMask(false, false, false, false);
                } else {
                    glColorMask(true, true, true, true);
                }

                c.render(ChunkMesh.RENDER_TYPE.WATER);
            }
        }

        _mobManager.renderAll();

        ShaderManager.getInstance().enableShader("block");
        _bulletPhysicsRenderer.render();

        ShaderManager.getInstance().enableShader(null);
    }

    public void update() {
        // Make sure the chunks near the player are generated first
        _worldProvider.getRenderingReferencePoint().set(_player.getPosition());

        updateTick();

        _skysphere.update();
        _player.update();
        _mobManager.updateAll();

        _bulletPhysicsRenderer.resetChunks();

        // Update the list of relevant chunks
        updateChunksInProximity();
        updateVisibleChunks();

        for (int i = 0; i < 16 && i < _chunksInProximity.size(); i++) {
            /* PHYSICS */
            if (_chunksInProximity.get(i).getActiveChunkMesh() != null) {
                if (_chunksInProximity.get(i).getActiveChunkMesh()._bulletMeshShape != null) {
                    Vector3f position = new Vector3f(_chunksInProximity.get(i).getPosition());
                    position.x *= Chunk.getChunkDimensionX();
                    position.y *= Chunk.getChunkDimensionY();
                    position.z *= Chunk.getChunkDimensionZ();

                    _bulletPhysicsRenderer.addStaticChunk(position,
                            _chunksInProximity.get(i).getActiveChunkMesh()._bulletMeshShape);
                }
            }
            /* ------ */
        }

        _bulletPhysicsRenderer.update();

        // Update the particle emitters
        _blockParticleEmitter.update();

        // Free unused space
        _worldProvider.getChunkProvider().flushCache();

        // And finally fire any active events
        _worldTimeEventManager.fireWorldTimeEvents();

        // Does nothing at the moment
        //_blockGrid.update();

        /* SIMULATE! */
        if (Blockmania.getInstance().getTime() - _lastSimulationStep > 1000) {
            _worldProvider.simulate();
            _lastSimulationStep = Blockmania.getInstance().getTime();
        }
    }

    /**
     * Performs and maintains tick-based logic. If the game is paused this logic is not executed
     * First effect: update the _tick variable that animation is based on
     * Secondary effect: Trigger spawning (via PortalManager) once every second
     * Tertiary effect: Trigger socializing (via MobManager) once every 10 seconds
     */
    private void updateTick() {
        // Update the animation tick
        _tick++;

        // This block is based on seconds or less frequent timings
        if (Blockmania.getInstance().getTime() - _lastTick >= 1000) {
            _tickTock++;
            _lastTick = Blockmania.getInstance().getTime();

            // PortalManager ticks for spawning once a second
            _portalManager.tickSpawn();

            // MobManager ticks for AI every 10 seconds
            if (_tickTock % 10 == 0) {
                _mobManager.tickAI();
            }
        }
    }

    /**
     * Returns the maximum height at a given position.
     *
     * @param x The X-coordinate
     * @param z The Z-coordinate
     * @return The maximum height
     */
    public final int maxHeightAt(int x, int z) {
        for (int y = Chunk.getChunkDimensionY() - 1; y >= 0; y--) {
            if (_worldProvider.getBlock(x, y, z) != 0x0)
                return y;
        }

        return 0;
    }

    /**
     * Chunk position of the player.
     *
     * @return The player offset on the x-axis
     */
    private int calcPlayerChunkOffsetX() {
        return (int) (_player.getPosition().x / Chunk.getChunkDimensionX());
    }

    /**
     * Chunk position of the player.
     *
     * @return The player offset on the z-axis
     */
    private int calcPlayerChunkOffsetZ() {
        return (int) (_player.getPosition().z / Chunk.getChunkDimensionZ());
    }

    /**
     * Sets a new player and spawns him at the spawning point.
     *
     * @param p The player
     */
    public void setPlayer(Player p) {
        if (_player != null) {
            _player.unregisterObserver(_chunkUpdateManager);
            _player.unregisterObserver(_bulletPhysicsRenderer);
        }

        _player = p;
        _player.registerObserver(_chunkUpdateManager);
        _player.registerObserver(_bulletPhysicsRenderer);

        _player.setSpawningPoint(_worldProvider.nextSpawningPoint());
        _player.reset();
        _player.respawn();
    }

    /**
     * Creates the first Portal if it doesn't exist yet
     */
    public void initPortal() {
        if (!_portalManager.hasPortal()) {
            // the y is hard coded because it always gets set to 32, deep underground
            Vector3f loc = new Vector3f(_player.getPosition().x, _player.getPosition().y + 4,
                    _player.getPosition().z);
            Blockmania.getInstance().getLogger().log(Level.INFO, "Portal location is" + loc);
            _worldProvider.setBlock((int) loc.x - 1, (int) loc.y, (int) loc.z, (byte) 30, false, false, true);
            _portalManager.addPortal(loc);
        }
    }

    /**
     * Disposes this world.
     */
    public void dispose() {
        _worldProvider.dispose();
        AudioManager.getInstance().stopAllSounds();
    }

    @Override
    public String toString() {
        return String.format(
                "world (biome: %s, time: %f, sun: %f, cache: %d, cu-duration: %fms, seed: \"%s\", title: \"%s\")",
                getActiveBiome(), _worldProvider.getTime(), _skysphere.getSunPosAngle(),
                _worldProvider.getChunkProvider().size(), _chunkUpdateManager.getAverageUpdateDuration(),
                _worldProvider.getSeed(), _worldProvider.getTitle());
    }

    public Player getPlayer() {
        return _player;
    }

    public boolean isChunkVisible(Chunk c) {
        return _player.getActiveCamera().getViewFrustum().intersects(c.getAABB());
    }

    public boolean isEntityVisible(Entity e) {
        return _player.getActiveCamera().getViewFrustum().intersects(e.getAABB());
    }

    public float getDaylight() {
        return _skysphere.getDaylight();
    }

    public BlockParticleEmitter getBlockParticleEmitter() {
        return _blockParticleEmitter;
    }

    public ChunkGeneratorTerrain.BIOME_TYPE getActiveBiome() {
        return _worldProvider.getActiveBiome((int) _player.getPosition().x, (int) _player.getPosition().z);
    }

    public double getActiveHumidity() {
        return _worldProvider.getHumidityAt((int) _player.getPosition().x, (int) _player.getPosition().z);
    }

    public double getActiveTemperature() {
        return _worldProvider.getTemperatureAt((int) _player.getPosition().x, (int) _player.getPosition().z);
    }

    public WorldProvider getWorldProvider() {
        return _worldProvider;
    }

    public BlockGrid getBlockGrid() {
        return _blockGrid;
    }

    public MobManager getMobManager() {
        return _mobManager;
    }

    public BulletPhysicsRenderer getRigidBlocksRenderer() {
        return _bulletPhysicsRenderer;
    }

    public int getTick() {
        return _tick;
    }

    public int getViewingDistance() {
        return _viewingDistance;
    }

    public void setViewingDistance(int distance) {
        _viewingDistance = distance;
        Blockmania.getInstance().resetOpenGLParameters();
    }
}