/*
* Copyright 2015 MovingBlocks
*
* 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.players;
import org.terasology.engine.Time;
import org.terasology.entitySystem.entity.EntityBuilder;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent;
import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent;
import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent;
import org.terasology.entitySystem.event.ReceiveEvent;
import org.terasology.entitySystem.systems.BaseComponentSystem;
import org.terasology.entitySystem.systems.RegisterMode;
import org.terasology.entitySystem.systems.RegisterSystem;
import org.terasology.entitySystem.systems.UpdateSubscriberSystem;
import org.terasology.logic.characters.CharacterComponent;
import org.terasology.logic.characters.CharacterHeldItemComponent;
import org.terasology.logic.console.commandSystem.annotations.Command;
import org.terasology.logic.console.commandSystem.annotations.CommandParam;
import org.terasology.logic.location.Location;
import org.terasology.logic.location.LocationComponent;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.Quat4f;
import org.terasology.math.geom.Vector3f;
import org.terasology.network.ClientComponent;
import org.terasology.registry.In;
import org.terasology.rendering.world.WorldRenderer;
@RegisterSystem(RegisterMode.CLIENT)
public class FirstPersonClientSystem extends BaseComponentSystem implements UpdateSubscriberSystem {
private static final int USEANIMATIONLENGTH = 200;
@In
private LocalPlayer localPlayer;
@In
private WorldRenderer worldRenderer;
@In
private EntityManager entityManager;
@In
private Time time;
private EntityRef handEntity;
private EntityRef currentHeldItem;
private boolean relinkHeldItem;
private EntityRef getHandEntity() {
if (handEntity == null) {
// create the hand entity
EntityBuilder entityBuilder = entityManager.newBuilder("engine:hand");
entityBuilder.setPersistent(false);
handEntity = entityBuilder.build();
}
return handEntity;
}
@ReceiveEvent
public void ensureClientSideEntityOnHeldItemMountPoint(OnActivatedComponent event, EntityRef camera,
FirstPersonHeldItemMountPointComponent firstPersonHeldItemMountPointComponent) {
if (!firstPersonHeldItemMountPointComponent.mountPointEntity.exists()) {
EntityBuilder builder = entityManager.newBuilder("engine:FirstPersonHeldItemMountPoint");
builder.setPersistent(false);
firstPersonHeldItemMountPointComponent.mountPointEntity = builder.build();
camera.saveComponent(firstPersonHeldItemMountPointComponent);
}
// link the mount point entity to the camera
Location.removeChild(camera, firstPersonHeldItemMountPointComponent.mountPointEntity);
Location.attachChild(camera, firstPersonHeldItemMountPointComponent.mountPointEntity,
firstPersonHeldItemMountPointComponent.translate,
new Quat4f(
TeraMath.DEG_TO_RAD * firstPersonHeldItemMountPointComponent.rotateDegrees.y,
TeraMath.DEG_TO_RAD * firstPersonHeldItemMountPointComponent.rotateDegrees.x,
TeraMath.DEG_TO_RAD * firstPersonHeldItemMountPointComponent.rotateDegrees.z),
firstPersonHeldItemMountPointComponent.scale);
}
@ReceiveEvent
public void ensureHeldItemIsMountedOnLoad(OnChangedComponent event, EntityRef entityRef, ClientComponent clientComponent) {
if (localPlayer.getClientEntity().equals(entityRef) && localPlayer.getCharacterEntity().exists() && localPlayer.getCameraEntity().exists()) {
CharacterHeldItemComponent characterHeldItemComponent = localPlayer.getCharacterEntity().getComponent(CharacterHeldItemComponent.class);
if (characterHeldItemComponent != null) {
// special case of sending in null so that the initial load works
linkHeldItemLocationForLocalPlayer(localPlayer.getCharacterEntity(), characterHeldItemComponent.selectedItem, null);
}
}
}
@Command(shortDescription = "Sets the held item mount point translation for the first person view")
public void setFirstPersonheldItemMountPointTranslation(@CommandParam("x") float x, @CommandParam("y") float y, @CommandParam("z") float z) {
FirstPersonHeldItemMountPointComponent newComponent = localPlayer.getCameraEntity().getComponent(FirstPersonHeldItemMountPointComponent.class);
if (newComponent != null) {
newComponent.translate = new Vector3f(x, y, z);
ensureClientSideEntityOnHeldItemMountPoint(OnActivatedComponent.newInstance(), localPlayer.getCameraEntity(), newComponent);
}
}
@Command(shortDescription = "Sets the held item mount point rotation for the first person view")
public void setFirstPersonheldItemMountPointRotation(@CommandParam("x") float x, @CommandParam("y") float y, @CommandParam("z") float z) {
FirstPersonHeldItemMountPointComponent newComponent = localPlayer.getCameraEntity().getComponent(FirstPersonHeldItemMountPointComponent.class);
if (newComponent != null) {
newComponent.rotateDegrees = new Vector3f(x, y, z);
ensureClientSideEntityOnHeldItemMountPoint(OnActivatedComponent.newInstance(), localPlayer.getCameraEntity(), newComponent);
}
}
@ReceiveEvent
public void onHeldItemActivated(OnActivatedComponent event, EntityRef character, CharacterHeldItemComponent heldItemComponent, CharacterComponent characterComponents) {
if (localPlayer.getCharacterEntity().equals(character)) {
EntityRef oldHeldItem = currentHeldItem;
currentHeldItem = heldItemComponent.selectedItem;
linkHeldItemLocationForLocalPlayer(character, currentHeldItem, oldHeldItem);
}
}
@ReceiveEvent
public void onHeldItemChanged(OnChangedComponent event, EntityRef character, CharacterHeldItemComponent heldItemComponent, CharacterComponent characterComponents) {
if (localPlayer.getCharacterEntity().equals(character)) {
EntityRef oldHeldItem = currentHeldItem;
currentHeldItem = heldItemComponent.selectedItem;
linkHeldItemLocationForLocalPlayer(character, currentHeldItem, oldHeldItem);
}
}
@ReceiveEvent(netFilter = RegisterMode.CLIENT)
public void onLocationRemovedAddClientSideLocation(BeforeDeactivateComponent event, EntityRef entityRef, LocationComponent locationComponent) {
// when in a remote client situation, the remove LocationComponent happens after the CharacterHeldItemComponent is changed.
// So this allows re-adding the client side LocationComponent after this
if (entityRef.equals(currentHeldItem)) {
relinkHeldItem = true;
}
}
void linkHeldItemLocationForLocalPlayer(EntityRef character, EntityRef newItem, EntityRef oldItem) {
if (!newItem.equals(oldItem)) {
EntityRef camera = localPlayer.getCameraEntity();
FirstPersonHeldItemMountPointComponent mountPointComponent = camera.getComponent(FirstPersonHeldItemMountPointComponent.class);
if (mountPointComponent != null) {
// remove the location from the old item
if (oldItem != null && oldItem.exists()) {
Location.removeChild(mountPointComponent.mountPointEntity, oldItem);
oldItem.removeComponent(LocationComponent.class);
oldItem.removeComponent(ItemIsHeldComponent.class);
} else {
Location.removeChild(mountPointComponent.mountPointEntity, getHandEntity());
getHandEntity().removeComponent(LocationComponent.class);
getHandEntity().removeComponent(ItemIsHeldComponent.class);
}
// use the hand if there is no new item
EntityRef heldItem = newItem;
if (!heldItem.exists()) {
heldItem = getHandEntity();
}
//ensure the item has a location
heldItem.addOrSaveComponent(new LocationComponent());
heldItem.addOrSaveComponent(new ItemIsHeldComponent());
FirstPersonHeldItemTransformComponent heldItemTransformComponent = heldItem.getComponent(FirstPersonHeldItemTransformComponent.class);
if (heldItemTransformComponent == null) {
heldItemTransformComponent = new FirstPersonHeldItemTransformComponent();
}
Location.attachChild(mountPointComponent.mountPointEntity, heldItem,
heldItemTransformComponent.translate,
new Quat4f(
TeraMath.DEG_TO_RAD * heldItemTransformComponent.rotateDegrees.y,
TeraMath.DEG_TO_RAD * heldItemTransformComponent.rotateDegrees.x,
TeraMath.DEG_TO_RAD * heldItemTransformComponent.rotateDegrees.z),
heldItemTransformComponent.scale);
}
}
}
/**
* modifies the held item mount point to move the held item in first person view
*/
@Override
public void update(float delta) {
// relink the held item if its location got removed from an item transition that removed its location
if (relinkHeldItem) {
linkHeldItemLocationForLocalPlayer(localPlayer.getCharacterEntity(), currentHeldItem, null);
relinkHeldItem = false;
}
// ensure that there are no lingering items that are marked as still held. This situation happens with client side predicted items
for (EntityRef entityRef : entityManager.getEntitiesWith(ItemIsHeldComponent.class)) {
if (!entityRef.equals(currentHeldItem) && !entityRef.equals(handEntity)) {
entityRef.removeComponent(ItemIsHeldComponent.class);
// also remove the location if it is currently location linked to the mount point
EntityRef camera = localPlayer.getCameraEntity();
FirstPersonHeldItemMountPointComponent mountPointComponent = camera.getComponent(FirstPersonHeldItemMountPointComponent.class);
LocationComponent locationComponent = entityRef.getComponent(LocationComponent.class);
if (mountPointComponent != null && locationComponent != null && locationComponent.getParent().equals(mountPointComponent.mountPointEntity)) {
entityRef.removeComponent(LocationComponent.class);
}
}
}
// get the first person mount point and rotate it away from the camera
CharacterHeldItemComponent characterHeldItemComponent = localPlayer.getCharacterEntity().getComponent(CharacterHeldItemComponent.class);
FirstPersonHeldItemMountPointComponent mountPointComponent = localPlayer.getCameraEntity().getComponent(FirstPersonHeldItemMountPointComponent.class);
if (characterHeldItemComponent == null
|| mountPointComponent == null) {
return;
}
LocationComponent locationComponent = mountPointComponent.mountPointEntity.getComponent(LocationComponent.class);
if (locationComponent == null) {
return;
}
long timeElapsedSinceLastUsed = time.getGameTimeInMs() - characterHeldItemComponent.lastItemUsedTime;
float animateAmount = 0f;
if (timeElapsedSinceLastUsed < USEANIMATIONLENGTH) {
// half way through the animation will be the maximum extent of rotation and translation
animateAmount = 1f - Math.abs(((float) timeElapsedSinceLastUsed / (float) USEANIMATIONLENGTH) - 0.5f);
}
float addPitch = 15f * animateAmount;
float addYaw = 10f * animateAmount;
locationComponent.setLocalRotation(new Quat4f(
TeraMath.DEG_TO_RAD * (mountPointComponent.rotateDegrees.y + addYaw),
TeraMath.DEG_TO_RAD * (mountPointComponent.rotateDegrees.x + addPitch),
TeraMath.DEG_TO_RAD * mountPointComponent.rotateDegrees.z));
Vector3f offset = new Vector3f(0.25f * animateAmount, -0.12f * animateAmount, 0f);
offset.add(mountPointComponent.translate);
locationComponent.setLocalPosition(offset);
mountPointComponent.mountPointEntity.saveComponent(locationComponent);
}
}