/*
* This file is part of Vanilla.
*
* Copyright (c) 2011 Spout LLC <http://www.spout.org/>
* Vanilla is licensed under the Spout License Version 1.
*
* Vanilla is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* In addition, 180 days after any changes are published, you can use the
* software, incorporating those changes, under the terms of the MIT license,
* as described in the Spout License Version 1.
*
* Vanilla 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 Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License,
* the MIT license and the Spout License Version 1 along with this program.
* If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
* License and see <http://spout.in/licensev1> for the full license, including
* the MIT license.
*/
package org.spout.vanilla.component.entity.living;
import java.util.Random;
import org.spout.api.component.entity.TextModelComponent;
import org.spout.api.data.Data;
import org.spout.api.entity.Entity;
import org.spout.api.entity.Player;
import org.spout.api.geo.discrete.Point;
import org.spout.api.geo.discrete.Transform;
import org.spout.api.inventory.ItemStack;
import org.spout.api.inventory.Slot;
import org.spout.math.GenericMath;
import org.spout.math.vector.Vector2f;
import org.spout.math.vector.Vector3f;
import org.spout.vanilla.component.entity.inventory.PlayerInventory;
import org.spout.vanilla.component.entity.misc.Digging;
import org.spout.vanilla.component.entity.misc.EntityHead;
import org.spout.vanilla.component.entity.misc.Health;
import org.spout.vanilla.component.entity.misc.MetadataComponent;
import org.spout.vanilla.component.entity.misc.PlayerItemCollector;
import org.spout.vanilla.component.entity.substance.Item;
import org.spout.vanilla.data.GameMode;
import org.spout.vanilla.data.Metadata;
import org.spout.vanilla.data.VanillaData;
import org.spout.vanilla.data.ViewDistance;
import org.spout.vanilla.data.configuration.VanillaConfiguration;
import org.spout.vanilla.data.configuration.WorldConfigurationNode;
import org.spout.vanilla.event.entity.HumanAbilityChangeEvent;
import org.spout.vanilla.event.player.PlayerGameModeChangedEvent;
import org.spout.vanilla.event.player.network.PlayerAbilityUpdateEvent;
import org.spout.vanilla.event.player.network.PlayerGameStateEvent;
import org.spout.vanilla.material.block.liquid.Water;
import org.spout.vanilla.protocol.entity.HumanEntityProtocol;
import org.spout.vanilla.protocol.msg.player.PlayerGameStateMessage;
/**
* A component that identifies the entity as a Vanilla player.
*/
public class Human extends Living {
@Override
public void onAttached() {
super.onAttached();
Entity holder = getOwner();
holder.add(PlayerItemCollector.class);
holder.add(Digging.class);
setEntityProtocol(new HumanEntityProtocol());
if (getAttachedCount() == 1) {
holder.add(Health.class).setSpawnHealth(20);
}
TextModelComponent textModel = getOwner().get(TextModelComponent.class);
if (textModel != null) {
textModel.setSize(0.5f);
textModel.setTranslation(new Vector3f(0, 3f, 0));
}
//holder.getPhysics().activate(1, new BoxShape(1f, 2.3f, 1f), false, true);
// Add metadata associated with the amount of arrows attached to the body
holder.add(MetadataComponent.class).addMeta(Metadata.TYPE_BYTE, 10, VanillaData.ARROWS_IN_BODY);
}
public byte getArrowsInBody() {
return getData().get(VanillaData.ARROWS_IN_BODY);
}
public void setArrowsInBody(byte amount) {
getData().put(VanillaData.ARROWS_IN_BODY, amount);
}
public ViewDistance getViewDistance() {
return getData().get(VanillaData.VIEW_DISTANCE);
}
public void setViewDistance(ViewDistance distance) {
getData().put(VanillaData.VIEW_DISTANCE, distance);
WorldConfigurationNode config = VanillaConfiguration.WORLDS.get(getOwner().getWorld().getName());
int viewDistance;
switch (distance) {
case FAR:
viewDistance = config.FAR_VIEW_DISTANCE.getInt();
break;
case NORMAL:
viewDistance = config.NORMAL_VIEW_DISTANCE.getInt();
break;
case SHORT:
viewDistance = config.SHORT_VIEW_DISTANCE.getInt();
break;
case TINY:
viewDistance = config.TINY_VIEW_DISTANCE.getInt();
break;
default:
viewDistance = config.NORMAL_VIEW_DISTANCE.getInt();
break;
}
//TODO: Client View distance, set it here
}
public boolean isAdventure() {
return getGameMode() == GameMode.ADVENTURE;
}
public boolean isCreative() {
return getGameMode() == GameMode.CREATIVE;
}
public boolean isSurvival() {
return getGameMode() == GameMode.SURVIVAL;
}
public boolean isSprinting() {
return getOwner().getData().get(VanillaData.IS_SPRINTING);
}
public void setSprinting(boolean isSprinting) {
getOwner().getData().put(VanillaData.IS_SPRINTING, isSprinting);
}
public boolean isFalling() {
return getOwner().getData().get(VanillaData.IS_FALLING);
}
public void setFalling(boolean isFalling) {
getOwner().getData().put(VanillaData.IS_FALLING, isFalling);
}
public boolean isJumping() {
return getOwner().getData().get(VanillaData.IS_JUMPING);
}
public void setJumping(boolean isJumping) {
getOwner().getData().put(VanillaData.IS_JUMPING, isJumping);
}
public boolean isInWater() {
return getOwner().getData().get(VanillaData.IS_IN_WATER);
}
public void setInWater(boolean inWater) {
getOwner().getData().put(VanillaData.IS_IN_WATER, inWater);
}
public String getName() {
return getData().get(Data.NAME);
}
public void setName(String name) {
getData().put(Data.NAME, name);
TextModelComponent textModel = getOwner().get(TextModelComponent.class);
if (textModel != null) {
textModel.setText(name);
}
}
public boolean isOp() {
return getOwner() instanceof Player && VanillaConfiguration.OPS.isOp(getName());
}
/**
* Drops the item specified into the direction the player looks, with slight randomness
*
* @param item to drop
*/
public void dropItem(ItemStack item) {
final Transform dropFrom;
EntityHead head = getHead();
if (head != null) {
dropFrom = head.getHeadTransform();
} else {
dropFrom = getOwner().getPhysics().getTransform();
}
// Some constants
final double impulseForce = 0.3;
final float maxXZForce = 0.02f;
final float maxYForce = 0.1f;
// Create a velocity vector using the transform, apply (random) force
Vector3f impulse = dropFrom.getRotation().getDirection().mul(impulseForce);
// Random rotational offset to avoid dropping at the same position
Random rand = GenericMath.getRandom();
float xzLength = maxXZForce * rand.nextFloat();
float yLength = maxYForce * (rand.nextFloat() - rand.nextFloat());
impulse = impulse.add(Vector2f.createRandomDirection(rand).mul(xzLength).toVector3(yLength));
// Slightly dropping upwards
impulse = impulse.add(0.0, 0.1, 0.0);
// Conversion factor, some sort of unit problem
//TODO: This needs an actual value and this value might change when gravity changes!
impulse = impulse.mul(100);
// Finally drop using a 4 second pickup delay
Item spawnedItem = Item.drop(dropFrom.getPosition(), item, impulse);
spawnedItem.setUncollectableDelay(4000);
}
/**
* Drops the player's current item.
*/
public void dropItem() {
PlayerInventory inventory = getOwner().get(PlayerInventory.class);
if (inventory != null) {
Slot selected = inventory.getQuickbar().getSelectedSlot();
ItemStack drop = selected.get();
if (drop == null) {
return;
} else {
drop = drop.clone().setAmount(1);
}
selected.addAmount(-1);
dropItem(drop);
}
}
// Abilities
public void setFlying(boolean isFlying, boolean updateClient) {
Boolean previous = getOwner().getData().put(VanillaData.IS_FLYING, isFlying);
if (callAbilityChangeEvent().isCancelled()) {
getOwner().getData().put(VanillaData.IS_FLYING, previous);
return;
}
updateAbilities(updateClient);
}
public void setFlying(boolean isFlying) {
setFlying(isFlying, true);
}
public boolean isFlying() {
return getOwner().getData().get(VanillaData.IS_FLYING);
}
public void setFlyingSpeed(float speed, boolean updateClient) {
Number value = getOwner().getData().put(VanillaData.FLYING_SPEED, speed);
float previous = value == null ? VanillaData.FLYING_SPEED.getDefaultValue().byteValue() : value.byteValue();
if (callAbilityChangeEvent().isCancelled()) {
getOwner().getData().put(VanillaData.FLYING_SPEED, previous);
return;
}
updateAbilities(updateClient);
}
public void setFlyingSpeed(float speed) {
setFlyingSpeed(speed, true);
}
public float getFlyingSpeed() {
return getOwner().getData().get(VanillaData.FLYING_SPEED).floatValue();
}
public void setWalkingSpeed(float speed, boolean updateClient) {
float previous = getOwner().getData().put(VanillaData.WALKING_SPEED, speed).floatValue();
if (callAbilityChangeEvent().isCancelled()) {
getOwner().getData().put(VanillaData.WALKING_SPEED, previous);
return;
}
updateAbilities(updateClient);
}
public void setWalkingSpeed(float speed) {
setWalkingSpeed(speed, true);
}
public float getWalkingSpeed() {
return getOwner().getData().get(VanillaData.WALKING_SPEED).floatValue();
}
public void setCanFly(boolean canFly, boolean updateClient) {
Boolean previous = getOwner().getData().put(VanillaData.CAN_FLY, canFly);
if (callAbilityChangeEvent().isCancelled()) {
getOwner().getData().put(VanillaData.CAN_FLY, previous);
return;
}
updateAbilities(updateClient);
}
public void setCanFly(boolean canFly) {
setCanFly(canFly, true);
}
public boolean canFly() {
return getOwner().getData().get(VanillaData.CAN_FLY);
}
public void setGodMode(boolean godMode, boolean updateClient) {
Boolean previous = getOwner().getData().put(VanillaData.GOD_MODE, godMode);
if (callAbilityChangeEvent().isCancelled()) {
getOwner().getData().put(VanillaData.GOD_MODE, previous);
return;
}
updateAbilities(updateClient);
}
public void setGodMode(boolean godMode) {
setGodMode(godMode, true);
}
public boolean getGodMode() {
return getOwner().getData().get(VanillaData.GOD_MODE);
}
public void setCreativeMode(boolean creative, boolean updateClient) {
if (creative) {
setGamemode(GameMode.CREATIVE, updateClient);
} else {
setGamemode(GameMode.SURVIVAL, updateClient);
}
}
public void setCreativeMode(boolean creative) {
setCreativeMode(creative, true);
}
public void setGamemode(GameMode mode, boolean updateClient) {
boolean changeToFromCreative = getGameMode() == GameMode.CREATIVE;
Entity holder = getOwner();
if (holder instanceof Player) {
PlayerGameModeChangedEvent event = holder.getEngine().getEventManager().callEvent(new PlayerGameModeChangedEvent((Player) getOwner(), mode));
if (event.isCancelled()) {
return;
}
changeToFromCreative ^= event.getMode() == GameMode.CREATIVE;
GameMode old = getGameMode();
mode = event.getMode();
//In Survival we shoudn't be able to fly.
setCanFly(mode == GameMode.CREATIVE, updateClient);
if (changeToFromCreative) {
if (callAbilityChangeEvent().isCancelled()) {
mode = old;
}
}
if (updateClient) {
holder.getNetwork().callProtocolEvent(new PlayerGameStateEvent((Player) holder, PlayerGameStateMessage.CHANGE_GAME_MODE, mode), (Player) getOwner());
}
}
getData().put(VanillaData.GAMEMODE, mode);
}
public void setGamemode(GameMode mode) {
setGamemode(mode, true);
}
public GameMode getGameMode() {
return getData().get(VanillaData.GAMEMODE);
}
public HumanAbilityChangeEvent callAbilityChangeEvent() {
return getOwner().getEngine().getEventManager().callEvent(new HumanAbilityChangeEvent(this));
}
// This is here to eliminate repetitive code above
private void updateAbilities(boolean updateClient) {
if (!updateClient || !(getOwner() instanceof Player)) {
return;
}
((Player) getOwner()).getNetwork().callProtocolEvent(new PlayerAbilityUpdateEvent((Player) getOwner()));
}
public void updateAbilities() {
updateAbilities(true);
}
@Override
public boolean canTick() {
return true;
}
@Override
public void onTick(float dt) {
super.onTick(dt);
final Point position = getOwner().getPhysics().getPosition();
setInWater(position.getBlock().getMaterial() instanceof Water);
}
}