org.terasology.logic.characters.Player.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.logic.characters.Player.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 org.terasology.logic.characters;

import org.lwjgl.BufferUtils;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.terasology.game.Terasology;
import org.terasology.logic.manager.ConfigurationManager;
import org.terasology.logic.manager.ShaderManager;
import org.terasology.logic.manager.TextureManager;
import org.terasology.logic.manager.ToolManager;
import org.terasology.logic.tools.Tool;
import org.terasology.logic.world.BlockObserver;
import org.terasology.logic.world.Chunk;
import org.terasology.model.blocks.Block;
import org.terasology.model.blocks.BlockManager;
import org.terasology.model.inventory.Inventory;
import org.terasology.model.inventory.Item;
import org.terasology.model.inventory.ItemBlock;
import org.terasology.model.inventory.Toolbar;
import org.terasology.model.structures.AABB;
import org.terasology.model.structures.BlockPosition;
import org.terasology.model.structures.RayBlockIntersection;
import org.terasology.rendering.cameras.Camera;
import org.terasology.rendering.cameras.FirstPersonCamera;
import org.terasology.rendering.physics.BulletPhysicsRenderer;
import org.terasology.rendering.primitives.Mesh;
import org.terasology.rendering.primitives.MeshCollection;
import org.terasology.rendering.primitives.Tessellator;
import org.terasology.rendering.world.WorldRenderer;
import org.terasology.utilities.MathHelper;

import javax.vecmath.Vector2f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4f;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collections;

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

/**
 * Extends the character class and provides support for player functionality. Also provides the
 * modelview matrix from the player's point of view.
 *
 * @author Benjamin Glatzel <benjamin.glatzel@me.com>
 */
public final class Player extends Character {

    /* CONSTANT VALUES */
    private static final double MOUSE_SENS = (Double) ConfigurationManager.getInstance().getConfig()
            .get("Controls.mouseSens");
    private static final boolean DEMO_FLIGHT = (Boolean) ConfigurationManager.getInstance().getConfig()
            .get("System.Debug.demoFlight");
    private static final boolean GOD_MODE = (Boolean) ConfigurationManager.getInstance().getConfig()
            .get("System.Debug.godMode");
    private static final boolean CAMERA_BOBBING = (Boolean) ConfigurationManager.getInstance().getConfig()
            .get("Player.cameraBobbing");

    private static final double WALKING_SPEED = (Double) ConfigurationManager.getInstance().getConfig()
            .get("Player.walkingSpeed");
    private static final boolean SHOW_PLACING_BOX = (Boolean) ConfigurationManager.getInstance().getConfig()
            .get("HUD.placingBox");
    private static final double RUNNING_FACTOR = (Double) ConfigurationManager.getInstance().getConfig()
            .get("Player.runningFactor");
    private static final double JUMP_INTENSITY = (Double) ConfigurationManager.getInstance().getConfig()
            .get("Player.jumpIntensity");

    /* OBSERVERS */
    private final ArrayList<BlockObserver> _observers = new ArrayList<BlockObserver>();

    /* CAMERA */
    private final FirstPersonCamera _firstPersonCamera = new FirstPersonCamera();
    private final Camera _activeCamera = _firstPersonCamera;

    /* INTERACTIONS */
    private long _lastInteraction;
    private byte _extractionCounter;
    private RayBlockIntersection.Intersection _selectedBlock, _extractedBlock;
    private float _handMovementAnimationOffset;
    private Mesh _handMesh, _overlayMesh;

    /* INVENTORY */
    private Inventory _inventory = new Inventory(this);
    private Toolbar _toolbar = new Toolbar(this);

    /* GOD MODE */
    private long _lastTimeSpacePressed;

    /* RESPAWNING */
    private long _timeOfDeath = -1;

    /* TOOLS */
    private final ToolManager _toolManager = new ToolManager(this);

    public Player(WorldRenderer parent) {
        super(parent, WALKING_SPEED, RUNNING_FACTOR, JUMP_INTENSITY);

        // Set the default value for the god mode
        _godMode = GOD_MODE;
    }

    public void render() {
        super.render();
        updateCameraParameters();

        // Display the block the player is aiming at
        if (SHOW_PLACING_BOX) {
            if (_selectedBlock != null) {
                if (BlockManager.getInstance()
                        .getBlock(_parent.getWorldProvider()
                                .getBlockAtPosition(_selectedBlock.getBlockPosition().toVector3d()))
                        .isRenderBoundingBox()) {
                    Block.AABBForBlockAt(_selectedBlock.getBlockPosition().toVector3d()).render(8f);
                }
            }
        }

        calcSelectedBlock();
    }

    public void update() {
        _walkingSpeed = WALKING_SPEED;

        if (_activeCamera != null) {
            _activeCamera.update();

            // Slightly adjust the field of view when flying
            if (_godMode) {
                _activeCamera.extendFov(10);
            } else {
                _activeCamera.resetFov();
            }
        }

        // Speedup if the player is playing god
        if (_godMode) {
            _walkingSpeed *= 1.5;
        }

        if (_handMovementAnimationOffset > 0) {
            _handMovementAnimationOffset -= 0.04;
        } else if (_handMovementAnimationOffset < 0) {
            _handMovementAnimationOffset = 0;
        }

        super.update();

        _selectedBlock = calcSelectedBlock();

        // Respawn the player after a certain amount of time
        if (isDead() && _timeOfDeath == -1) {
            _timeOfDeath = Terasology.getInstance().getTime();
        } else if (isDead() && Terasology.getInstance().getTime() - _timeOfDeath > 1000) {
            revive();
            respawn();
            _timeOfDeath = -1;
        }
    }

    /**
     * Processes interactions for the given mouse button.
     *
     * @param button The pressed mouse button
     */
    private void processInteractions(int button) {
        // Throttle interactions
        if (Terasology.getInstance().getTime() - _lastInteraction < 200) {
            return;
        }

        Tool activeTool = getActiveTool();

        if (activeTool != null) {
            // Check if one of the mouse buttons is pressed
            if (Mouse.isButtonDown(0) || button == 0) {
                activeTool.executeLeftClickAction();
                _lastInteraction = Terasology.getInstance().getTime();
                poke();
            } else if (Mouse.isButtonDown(1) || button == 1) {
                activeTool.executeRightClickAction();
                _lastInteraction = Terasology.getInstance().getTime();
                poke();
            }
        }
    }

    public void resetExtraction() {
        _extractedBlock = _selectedBlock;
        _extractionCounter = 0;
    }

    public void poke() {
        _handMovementAnimationOffset = 0.5f;
    }

    public void renderFirstPersonViewElements() {
        if (getActiveItem() != null) {
            if (getActiveItem().renderFirstPersonView()) {
                return;
            }
        }

        renderHand();
    }

    /**
     * Calculates the bobbing offset factor using sine/cosine functions. The offset adjusted linearly to
     * the player's movement speed.
     *
     * @param phaseOffset Phase offset
     * @param amplitude   Amplitude
     * @param frequency   Frequency
     * @return The bobbing offset
     */
    public double calcBobbingOffset(float phaseOffset, float amplitude, float frequency) {
        if (_godMode || DEMO_FLIGHT)
            return 0;

        float speedFactor = 1.0f;

        if (_activeWalkingSpeed > 0)
            speedFactor = (float) MathHelper
                    .clamp(Math.max(Math.abs(_velocity.x), Math.abs(_velocity.z)) / _activeWalkingSpeed);

        return Math.sin(_stepCounter * frequency + phaseOffset) * amplitude * speedFactor;
    }

    public void updateCameraParameters() {
        _firstPersonCamera.getPosition().set(calcEyeOffset());

        if (CAMERA_BOBBING) {
            _firstPersonCamera.setBobbingRotationOffsetFactor(calcBobbingOffset(0.0f, 0.01f, 2.2f));
            _firstPersonCamera
                    .setBobbingVerticalOffsetFactor(calcBobbingOffset((float) Math.PI / 4f, 0.025f, 4.4f));
        } else {
            _firstPersonCamera.setBobbingRotationOffsetFactor(0.0);
            _firstPersonCamera.setBobbingVerticalOffsetFactor(0.0);
        }

        if (!(DEMO_FLIGHT)) {
            _firstPersonCamera.getViewingDirection().set(getViewingDirection());
        } else {
            Vector3d viewingTarget = new Vector3d(getPosition().x, 40, getPosition().z - 128);
            _firstPersonCamera.getViewingDirection().sub(viewingTarget, getPosition());
        }
    }

    public void updatePosition() {
        // DEMO MODE
        if (DEMO_FLIGHT) {
            getPosition().z -= 0.2f;

            int maxHeight = _parent.maxHeightAt((int) getPosition().x, (int) getPosition().z + 8) + 16;

            getPosition().y += (maxHeight - getPosition().y) / 64f;

            if (getPosition().y > 128)
                getPosition().y = 128;

            if (getPosition().y < 40f)
                getPosition().y = 40f;

            return;
        }

        super.updatePosition();
    }

    /**
     * Calculates the currently targeted block in front of the player.
     *
     * @return Intersection point of the targeted block
     */
    public RayBlockIntersection.Intersection calcSelectedBlock() {
        ArrayList<RayBlockIntersection.Intersection> inters = new ArrayList<RayBlockIntersection.Intersection>();

        int blockPosX, blockPosY, blockPosZ;

        for (int x = -3; x <= 3; x++) {
            for (int y = -3; y <= 3; y++) {
                for (int z = -3; z <= 3; z++) {
                    // Make sure the correct block positions are calculated relatively to the position of the player
                    blockPosX = (int) (getPosition().x + (getPosition().x >= 0 ? 0.5f : -0.5f)) + x;
                    blockPosY = (int) (getPosition().y + (getPosition().y >= 0 ? 0.5f : -0.5f)) + y;
                    blockPosZ = (int) (getPosition().z + (getPosition().z >= 0 ? 0.5f : -0.5f)) + z;

                    byte blockType = _parent.getWorldProvider().getBlock(blockPosX, blockPosY, blockPosZ);

                    // Ignore special blocks
                    if (BlockManager.getInstance().getBlock(blockType).isSelectionRayThrough()) {
                        continue;
                    }

                    // The ray originates from the "player's eye"
                    ArrayList<RayBlockIntersection.Intersection> iss = RayBlockIntersection.executeIntersection(
                            _parent.getWorldProvider(), blockPosX, blockPosY, blockPosZ, calcEyePosition(),
                            _viewingDirection);

                    if (iss != null) {
                        inters.addAll(iss);
                    }
                }
            }
        }

        /**
         * Calculated the closest intersection.
         */
        if (inters.size() > 0) {
            Collections.sort(inters);
            return inters.get(0);
        }

        return null;
    }

    /**
     * Processes the keyboard input.
     *
     * @param key         Pressed key on the keyboard
     * @param state       The state of the key
     * @param repeatEvent True if repeat event
     */
    public void processKeyboardInput(int key, boolean state, boolean repeatEvent) {
        switch (key) {
        case Keyboard.KEY_K:
            if (!repeatEvent && state) {
                damage(9999);
            }
            break;
        case Keyboard.KEY_SPACE:
            if (!repeatEvent && state) {
                jump();

                if (Terasology.getInstance().getTime() - _lastTimeSpacePressed < 200) {
                    _godMode = !_godMode;
                }

                _lastTimeSpacePressed = Terasology.getInstance().getTime();
            }
            break;
        case Keyboard.KEY_1:
            _toolbar.setSelectedSlot(0);
            break;
        case Keyboard.KEY_2:
            _toolbar.setSelectedSlot(1);
            break;
        case Keyboard.KEY_3:
            _toolbar.setSelectedSlot(2);
            break;
        case Keyboard.KEY_4:
            _toolbar.setSelectedSlot(3);
            break;
        case Keyboard.KEY_5:
            _toolbar.setSelectedSlot(4);
            break;
        case Keyboard.KEY_6:
            _toolbar.setSelectedSlot(5);
            break;
        case Keyboard.KEY_7:
            _toolbar.setSelectedSlot(6);
            break;
        case Keyboard.KEY_8:
            _toolbar.setSelectedSlot(7);
            break;
        case Keyboard.KEY_9:
            _toolbar.setSelectedSlot(8);
            break;
        }
    }

    /**
     * Processes the mouse input.
     *
     * @param button     Pressed mouse button
     * @param state      State of the mouse button
     * @param wheelMoved Distance the mouse wheel moved since last
     */
    public void processMouseInput(int button, boolean state, int wheelMoved) {
        if (isDead())
            return;

        if (wheelMoved != 0) {
            _toolbar.rollSelectedSlot((byte) (wheelMoved / 120));
        } else if (state && (button == 0 || button == 1)) {
            processInteractions(button);
        }
    }

    /**
     * Checks for pressed keys and mouse movement and executes the respective movement
     * command.
     */
    public void processMovement() {

    }

    public void updateInput() {
        if (isDead())
            return;

        // Process interactions even if the mouse button is pressed down
        // and not fired by a repeated event
        processInteractions(-1);

        _movementDirection.set(0, 0, 0);

        double dx = Mouse.getDX();
        double dy = Mouse.getDY();

        yaw(dx * MOUSE_SENS);
        pitch(dy * MOUSE_SENS);

        if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
            walkForward();
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
            walkBackwards();
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
            strafeLeft();
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
            strafeRight();
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
            moveUp();
        }

        _running = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) && (_touchingGround || _godMode);
    }

    /**
     * Renders the actively selected block in the front of the player's first person perspective.
     */
    public void renderExtractionOverlay() {
        if (_extractionCounter <= 0 || _extractedBlock == null)
            return;

        Block block = BlockManager.getInstance().getBlock(
                _parent.getWorldProvider().getBlockAtPosition(_extractedBlock.getBlockPosition().toVector3d()));

        glEnable(GL_TEXTURE_2D);
        TextureManager.getInstance().bindTexture("effects");

        glEnable(GL11.GL_BLEND);
        glBlendFunc(GL_DST_COLOR, GL_ZERO);

        glPushMatrix();
        glTranslated(_extractedBlock.getBlockPosition().x - getPosition().x,
                _extractedBlock.getBlockPosition().y - getPosition().y,
                _extractedBlock.getBlockPosition().z - getPosition().z);

        float offset = Math.round(((float) _extractionCounter / block.getHardness()) * 10.0f) * 0.0625f;

        if (_overlayMesh == null) {
            Vector2f texPos = new Vector2f(0.0f, 0.0f);
            Vector2f texWidth = new Vector2f(0.0624f, 0.0624f);

            MeshCollection.addBlockMesh(new Vector4f(1, 1, 1, 1), texPos, texWidth, 1.001f, 1.0f, 1.0f, 0.0f, 0.0f,
                    0.0f);
            _overlayMesh = Tessellator.getInstance().generateMesh();
            Tessellator.getInstance().resetAll();
        }

        glMatrixMode(GL_TEXTURE);
        glPushMatrix();
        glTranslatef(offset, 0f, 0f);
        glMatrixMode(GL_MODELVIEW);

        _overlayMesh.render();

        glPopMatrix();

        glMatrixMode(GL_TEXTURE);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);

        glDisable(GL11.GL_BLEND);
        glDisable(GL_TEXTURE_2D);
    }

    /**
     * Renders a simple hand displayed in front of the player's first person perspective.
     */
    public void renderHand() {
        glEnable(GL11.GL_TEXTURE_2D);
        TextureManager.getInstance().bindTexture("char");
        ShaderManager.getInstance().enableShader("block");

        int light = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("block"), "light");
        GL20.glUniform1f(light, _parent.getRenderingLightValueAt(getPosition()));

        // Make sure the hand is not affected by the biome color. ZOMBIES!!!!
        FloatBuffer colorBuffer = BufferUtils.createFloatBuffer(3);
        colorBuffer.put(1.0f);
        colorBuffer.put(1.0f);
        colorBuffer.put(1.0f);
        colorBuffer.flip();

        int colorOffset = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("block"), "colorOffset");
        GL20.glUniform3(colorOffset, colorBuffer);

        glPushMatrix();
        glTranslatef(0.8f, -1.1f + (float) calcBobbingOffset((float) Math.PI / 8f, 0.05f, 2.5f)
                - _handMovementAnimationOffset * 0.5f, -1.0f - _handMovementAnimationOffset * 0.5f);
        glRotatef(-45f - _handMovementAnimationOffset * 64.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(35f, 0.0f, 1.0f, 0.0f);
        glTranslatef(0f, 0.25f, 0f);
        glScalef(0.3f, 0.6f, 0.3f);

        if (_handMesh == null) {
            Vector2f texPos = new Vector2f(40.0f * 0.015625f, 32.0f * 0.03125f);
            Vector2f texWidth = new Vector2f(4.0f * 0.015625f, -12.0f * 0.03125f);

            MeshCollection.addBlockMesh(new Vector4f(1, 1, 1, 1), texPos, texWidth, 1.0f, 1.0f, 0.5f, 0.0f, 0.0f,
                    0.0f);
            _handMesh = Tessellator.getInstance().generateMesh();
            Tessellator.getInstance().resetAll();
        }

        _handMesh.render();

        glPopMatrix();

        glDisable(GL11.GL_TEXTURE_2D);

        ShaderManager.getInstance().enableShader(null);

    }

    public Item getActiveItem() {
        return getToolbar().getItemForSelectedSlot();
    }

    public Block getActiveBlock() {
        Item item = getActiveItem();

        if (item != null) {
            if (item.getClass() == ItemBlock.class) {
                return BlockManager.getInstance().getBlock(((ItemBlock) getActiveItem()).getBlockId());
            }
        }

        return null;
    }

    public Tool getActiveTool() {
        if (getActiveItem() != null) {
            return _toolManager.getToolForIndex(getActiveItem().getToolId());
        }

        return _toolManager.getToolForIndex((byte) 0x01);
    }

    @Override
    public String toString() {
        return String.format(
                "player (x: %.2f, y: %.2f, z: %.2f | x: %.2f, y: %.2f, z: %.2f | gravity: %.2f | x: %.2f, y: %.2f, z: %.2f)",
                getPosition().x, getPosition().y, getPosition().z, _viewingDirection.x, _viewingDirection.y,
                _viewingDirection.z, _gravity, _movementDirection.x, _movementDirection.y, _movementDirection.z);
    }

    protected AABB generateAABBForPosition(Vector3d p) {
        return new AABB(p, new Vector3d(.3f, 0.8f, .3f));
    }

    public AABB getAABB() {
        return generateAABBForPosition(getPosition());
    }

    public Camera getActiveCamera() {
        return _activeCamera;
    }

    public void registerObserver(BlockObserver observer) {
        _observers.add(observer);
    }

    public void unregisterObserver(BlockObserver observer) {
        _observers.remove(observer);
    }

    public void notifyObserversBlockPlaced(Chunk chunk, BlockPosition pos) {
        for (BlockObserver ob : _observers)
            ob.blockPlaced(chunk, pos);

        BulletPhysicsRenderer.getInstance().blockPlaced(chunk, pos);
    }

    public void notifyObserversBlockRemoved(Chunk chunk, BlockPosition pos) {
        for (BlockObserver ob : _observers)
            ob.blockRemoved(chunk, pos);

        BulletPhysicsRenderer.getInstance().blockRemoved(chunk, pos);
    }

    public Inventory getInventory() {
        return _inventory;
    }

    public Toolbar getToolbar() {
        return _toolbar;
    }

    public ToolManager getToolManager() {
        return _toolManager;
    }

    public RayBlockIntersection.Intersection getSelectedBlock() {
        return _selectedBlock;
    }

    public RayBlockIntersection.Intersection getExtractedBlock() {
        return _extractedBlock;
    }

    public void setExtractedBlock(RayBlockIntersection.Intersection block) {
        _extractedBlock = block;
    }

    public void setExtractionCounter(byte counter) {
        _extractionCounter = counter;
    }

    public byte getExtractionCounter() {
        return _extractionCounter;
    }

    public float getHandMovementAnimationOffset() {
        return _handMovementAnimationOffset;
    }

    public boolean isCarryingTorch() {
        if (getActiveBlock() != null) {
            if (getActiveBlock().getLuminance() > 0)
                return true;
        }

        return false;
    }

}