/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.entity; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; import com.google.common.collect.ImmutableList; import org.lanternpowered.server.component.BaseComponentHolder; import org.lanternpowered.server.component.misc.Health; import org.lanternpowered.server.data.AbstractDataHolder; import org.lanternpowered.server.data.key.LanternKeys; import org.lanternpowered.server.data.property.AbstractPropertyHolder; import org.lanternpowered.server.data.value.KeyRegistration; import org.lanternpowered.server.entity.event.EntityEvent; import org.lanternpowered.server.entity.living.player.LanternPlayer; import org.lanternpowered.server.game.registry.type.entity.EntityTypeRegistryModule; import org.lanternpowered.server.network.entity.EntityProtocolType; import org.lanternpowered.server.text.LanternTexts; import org.lanternpowered.server.util.Quaternions; import org.lanternpowered.server.world.LanternWorld; import org.spongepowered.api.data.DataContainer; import org.spongepowered.api.data.DataHolder; import org.spongepowered.api.data.DataTransactionResult; import org.spongepowered.api.data.DataView; import org.spongepowered.api.data.key.Key; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.data.manipulator.DataManipulator; import org.spongepowered.api.data.persistence.InvalidDataException; import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.EntityArchetype; import org.spongepowered.api.entity.EntitySnapshot; import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.entity.Transform; import org.spongepowered.api.entity.living.Living; import org.spongepowered.api.event.cause.Cause; import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.translation.FixedTranslation; import org.spongepowered.api.text.translation.Translation; import org.spongepowered.api.util.AABB; import org.spongepowered.api.util.Direction; import org.spongepowered.api.util.RelativePositions; import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; public class LanternEntity extends BaseComponentHolder implements Entity, AbstractDataHolder, AbstractPropertyHolder { @SuppressWarnings("unused") private static boolean bypassEntityTypeLookup; // The unique id of this entity private final UUID uniqueId; // The entity type of this entity private final LanternEntityType entityType; // The random object of this entity private final Random random = new Random(); // The raw value map private final Map<Key<?>, KeyRegistration> rawValueMap = new HashMap<>(); private final Map<Class<?>, DataManipulator<?, ?>> rawAdditionalManipulators = new ConcurrentHashMap<>(); // The world this entity is located in, may be null private LanternWorld world; // The position of the entity private Vector3d position = Vector3d.ZERO; // The rotation of the entity private Vector3d rotation = Vector3d.ZERO; // The scale of the entity private Vector3d scale = Vector3d.ONE; /** * The entity protocol type of this entity. */ @Nullable private EntityProtocolType<?> entityProtocolType; /** * The state of the removal of this entity. */ @Nullable private RemoveState removeState; private boolean onGround; @Nullable private volatile Vector3i lastChunkCoords; /** * The base of the {@link AABB} of this entity. */ @Nullable private AABB boundingBoxBase; @Nullable private AABB boundingBox; @Nullable private volatile UUID creator; @Nullable private volatile UUID notifier; @Nullable private LanternEntity vehicle; private final List<LanternEntity> passengers = new ArrayList<>(); public enum RemoveState { /** * The entity was destroyed through the {@link #remove()} * method. Will not be respawned in any case. */ DESTROYED, /** * The entity was removed due chunk unloading. It will appear * as "removed", but it is basicly just unloaded. */ CHUNK_UNLOAD, } public LanternEntity(UUID uniqueId) { this.uniqueId = uniqueId; registerKeys(); if (!bypassEntityTypeLookup) { this.entityType = (LanternEntityType) EntityTypeRegistryModule.get().getByClass(this.getClass()).orElseThrow( () -> new IllegalStateException("Every entity class should be registered as a EntityType.")); } else { //noinspection ConstantConditions this.entityType = null; } } @Override public void registerKeys() { registerKey(Keys.DISPLAY_NAME, null); registerKey(Keys.CUSTOM_NAME_VISIBLE, true); registerKey(Keys.VELOCITY, Vector3d.ZERO).notRemovable(); registerKey(Keys.FIRE_TICKS, 0).notRemovable(); registerKey(Keys.FALL_DISTANCE, 0f).notRemovable(); registerKey(Keys.GLOWING, false).notRemovable(); registerKey(LanternKeys.INVULNERABLE, false).notRemovable(); registerKey(LanternKeys.PORTAL_COOLDOWN_TICKS, 0).notRemovable(); } /** * Gets the {@link Direction} that the entity is looking. * * @param division The division * @return The direction */ public Direction getDirection(Direction.Division division) { Vector3d rotation = this instanceof Living ? ((Living) this).getHeadRotation() : this.rotation; // Invert the x direction because west and east are swapped final Vector3d direction = Quaternions.fromAxesAnglesDeg(rotation).getDirection().mul(-1, 1, 1); return Direction.getClosest(direction, division); } public Vector3d getHorizontalDirectionVector() { final Vector3d rotation = this instanceof Living ? ((Living) this).getHeadRotation() : this.rotation; // Invert the x direction because west and east are swapped return Quaternions.fromAxesAnglesDeg(rotation.mul(0, 1, 0)).getDirection().mul(-1, 1, 1); } /** * Gets the {@link Direction} that the entity is looking in the horizontal plane. * * @param division The division * @return The direction */ public Direction getHorizontalDirection(Direction.Division division) { return Direction.getClosest(getHorizontalDirectionVector(), division); } @Nullable public EntityProtocolType<?> getEntityProtocolType() { return this.entityProtocolType; } public void setEntityProtocolType(@Nullable EntityProtocolType<?> entityProtocolType) { if (entityProtocolType != null) { checkArgument(entityProtocolType.getEntityType().isInstance(this), "The protocol type %s is not applicable to this entity."); } this.entityProtocolType = entityProtocolType; } @Override public boolean isRemoved() { return this.removeState != null; } @Override public void remove() { if (!isRemoved()) { remove(RemoveState.DESTROYED); } } @Nullable public RemoveState getRemoveState() { return this.removeState; } public void remove(RemoveState removeState) { checkNotNull(removeState, "removeState"); this.removeState = removeState; if (removeState == RemoveState.DESTROYED) { setVehicle(null); clearPassengers(); } } public void resurrect() { checkArgument(this.removeState != RemoveState.DESTROYED, "A destroyed entity cannot be resurrected/respawned."); this.removeState = null; } @Nullable public Vector3i getLastChunkSectionCoords() { return this.lastChunkCoords; } public void setLastChunkCoords(@Nullable Vector3i coords) { this.lastChunkCoords = coords; } @Override public boolean isOnGround() { return this.onGround; } /** * Sets the on ground state of this entity. * * @param onGround The on ground state */ public void setOnGround(boolean onGround) { this.onGround = onGround; } @Override public UUID getUniqueId() { return this.uniqueId; } public void setBoundingBoxBase(@Nullable AABB boundingBox) { this.boundingBoxBase = boundingBox; this.boundingBox = null; } @Override public Optional<AABB> getBoundingBox() { AABB boundingBox = this.boundingBox; if (boundingBox == null && this.boundingBoxBase != null) { boundingBox = this.boundingBoxBase.offset(this.position); this.boundingBox = boundingBox; } return Optional.ofNullable(boundingBox); } @Override public Map<Key<?>, KeyRegistration> getRawValueMap() { return this.rawValueMap; } @Override public Map<Class<?>, DataManipulator<?, ?>> getRawAdditionalContainers() { return this.rawAdditionalManipulators; } @Override public boolean validateRawData(DataView dataView) { // TODO Auto-generated method stub return false; } @Override public void setRawData(DataView dataView) throws InvalidDataException { // TODO Auto-generated method stub } @Override public int getContentVersion() { return 1; } @Override public DataContainer toContainer() { // TODO Auto-generated method stub return null; } @Override public EntityType getType() { return this.entityType; } @Override public LanternWorld getWorld() { return this.world; } protected void setWorld(@Nullable LanternWorld world) { this.world = world; } protected void setRawPosition(Vector3d position) { this.position = checkNotNull(position, "position"); this.boundingBox = null; } protected void setRawRotation(Vector3d rotation) { this.rotation = checkNotNull(rotation, "rotation"); } public Vector3d getPosition() { return this.position; } public void setPosition(Vector3d position) { setRawPosition(position); } public boolean setPositionAndWorld(World world, Vector3d position) { setRawPosition(position); setWorld((LanternWorld) world); // TODO: Events return true; } @Override public Location<World> getLocation() { checkState(this.world != null, "This entity doesn't have a world."); return new Location<>(this.world, this.position); } @Override public boolean setLocation(Location<World> location) { checkNotNull(location, "location"); return setPositionAndWorld(location.getExtent(), location.getPosition()); } @Override public Vector3d getScale() { return this.scale; } @Override public void setScale(Vector3d scale) { this.scale = checkNotNull(scale, "scale"); } @Override public Vector3d getRotation() { return this.rotation; } @Override public void setRotation(Vector3d rotation) { setRawRotation(rotation); } @Override public boolean transferToWorld(World world, Vector3d position) { return setPositionAndWorld(checkNotNull(world, "world"), position); } @Override public Transform<World> getTransform() { return new Transform<>(this.world, this.position, this.rotation); } @Override public boolean setTransform(Transform<World> transform) { checkNotNull(transform, "transform"); setLocationAndRotation(transform.getLocation(), transform.getRotation()); setScale(transform.getScale()); // TODO: Events return true; } @Override public boolean setLocationAndRotation(Location<World> location, Vector3d rotation) { checkNotNull(location, "location"); checkNotNull(rotation, "rotation"); setWorld((LanternWorld) location.getExtent()); setRawPosition(location.getPosition()); setRawRotation(rotation); // TODO: Events return true; } @Override public boolean setLocationAndRotation(Location<World> location, Vector3d rotation, EnumSet<RelativePositions> relativePositions) { checkNotNull(location, "location"); checkNotNull(rotation, "rotation"); checkNotNull(relativePositions, "relativePositions"); final World world = location.getExtent(); final Vector3d pos = location.getPosition(); double x = pos.getX(); double y = pos.getY(); double z = pos.getZ(); double pitch = rotation.getX(); double yaw = rotation.getY(); double roll = rotation.getZ(); if (relativePositions.contains(RelativePositions.X)) { x += this.position.getX(); } if (relativePositions.contains(RelativePositions.Y)) { y += this.position.getY(); } if (relativePositions.contains(RelativePositions.Z)) { z += this.position.getZ(); } if (relativePositions.contains(RelativePositions.PITCH)) { pitch += this.rotation.getX(); } if (relativePositions.contains(RelativePositions.YAW)) { yaw += this.rotation.getY(); } // TODO: No relative roll? setWorld((LanternWorld) world); setRawPosition(new Vector3d(x, y, z)); setRawRotation(new Vector3d(pitch, yaw, roll)); // TODO: Events return true; } @Override public List<Entity> getPassengers() { synchronized (this.passengers) { return ImmutableList.copyOf(this.passengers); } } @Override public DataTransactionResult addPassenger(Entity entity) { checkNotNull(entity, "entity"); final LanternEntity entity1 = (LanternEntity) entity; if (entity1.getVehicle0() != null) { return DataTransactionResult.failNoData(); } return entity1.setVehicle(this); } @Override public DataTransactionResult removePassenger(Entity entity) { checkNotNull(entity, "entity"); final LanternEntity entity1 = (LanternEntity) entity; if (entity1.getVehicle0() != this) { return DataTransactionResult.failNoData(); } return entity1.setVehicle(null); } @Override public DataTransactionResult clearPassengers() { synchronized (this.passengers) { for (LanternEntity passenger : this.passengers) { passenger.setVehicle(null); } } return DataTransactionResult.failNoData(); } @Override public Optional<Entity> getVehicle() { synchronized (this.passengers) { return Optional.ofNullable(this.vehicle); } } @Override public DataTransactionResult setVehicle(@Nullable Entity entity) { synchronized (this.passengers) { if (this.vehicle == entity) { return DataTransactionResult.failNoData(); } if (this.vehicle != null) { this.vehicle.removePassenger0(this); } this.vehicle = (LanternEntity) entity; if (this.vehicle != null) { this.vehicle.addPassenger0(this); } return DataTransactionResult.successNoData(); } } private void removePassenger0(LanternEntity passenger) { synchronized (this.passengers) { this.passengers.remove(passenger); } } private void addPassenger0(LanternEntity passenger) { synchronized (this.passengers) { int index = -1; if (passenger instanceof LanternPlayer) { do { index++; } while (index < this.passengers.size() && this.passengers.get(index) instanceof LanternPlayer); } if (index == -1) { this.passengers.add(passenger); } else { this.passengers.add(index, passenger); } } } @Override public LanternEntity getBaseVehicle() { synchronized (this.passengers) { LanternEntity lastEntity = this; while (true) { final LanternEntity entity = lastEntity.getVehicle0(); if (entity == null) { return lastEntity; } lastEntity = entity; } } } @Nullable private LanternEntity getVehicle0() { synchronized (this.passengers) { return this.vehicle; } } @Override public boolean isLoaded() { return this.removeState != RemoveState.CHUNK_UNLOAD; } /** * Pulses the entity. */ public void pulse() { synchronized (this.passengers) { if (this.vehicle != null) { this.position = this.vehicle.getPosition(); } } } @Override public EntitySnapshot createSnapshot() { // TODO Auto-generated method stub return null; } @Override public Random getRandom() { return this.random; } @Override public boolean damage(double damage, DamageSource damageSource, Cause cause) { Optional<Health> health = this.getComponent(Health.class); if (health.isPresent()) { return health.get().damage(damage, damageSource, cause); } return false; } @Override public Optional<UUID> getCreator() { return Optional.ofNullable(this.creator); } @Override public Optional<UUID> getNotifier() { return Optional.ofNullable(this.notifier); } @Override public void setCreator(@Nullable UUID uuid) { this.creator = uuid; } @Override public void setNotifier(@Nullable UUID uuid) { this.notifier = uuid; } @Override public Translation getTranslation() { final Optional<Text> displayName = this.get(Keys.DISPLAY_NAME); if (displayName.isPresent()) { return new FixedTranslation(LanternTexts.toLegacy(displayName.get())); } return this.entityType.getTranslation(); } @Override public DataHolder copy() { return null; } @Override public EntityArchetype createArchetype() { return null; } /** * Triggers the {@link EntityEvent} for this entity. * * @param event The event */ public void triggerEvent(EntityEvent event) { getWorld().getEntityProtocolManager().triggerEvent(this, event); } }