/* * Copyright 2017 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.entitySystem.ComponentContainer; import org.terasology.entitySystem.entity.EntityBuilder; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.logic.characters.CharacterComponent; import org.terasology.logic.location.Location; import org.terasology.logic.location.LocationComponent; import org.terasology.math.AABB; import org.terasology.math.TeraMath; import org.terasology.math.geom.BaseVector2i; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.SpiralIterable; import org.terasology.math.geom.Vector2i; import org.terasology.math.geom.Vector3f; import org.terasology.math.geom.Vector3i; import org.terasology.network.ClientComponent; import org.terasology.network.ColorComponent; import org.terasology.physics.shapes.BoxShapeComponent; import org.terasology.physics.shapes.CapsuleShapeComponent; import org.terasology.physics.shapes.CylinderShapeComponent; import org.terasology.physics.shapes.HullShapeComponent; import org.terasology.physics.shapes.SphereShapeComponent; import org.terasology.rendering.logic.MeshComponent; import org.terasology.world.WorldProvider; import java.util.Optional; /** * Creates new player instances. */ public class PlayerFactory { private static final Logger logger = LoggerFactory.getLogger(PlayerFactory.class); private EntityManager entityManager; private WorldProvider worldProvider; public PlayerFactory(EntityManager entityManager, WorldProvider worldProvider) { this.entityManager = entityManager; this.worldProvider = worldProvider; } /** * Creates a new player character entity. The desired spawning location is derived from * the {@link LocationComponent} of the controller. * @param controller the controlling client entity * @return a new player character entity */ public EntityRef newInstance(EntityRef controller) { EntityBuilder builder = entityManager.newBuilder("engine:player"); float extraSpace = 0.5f; // spawn a little bit above the ground float entityHeight = getHeightOf(builder) + extraSpace; LocationComponent location = controller.getComponent(LocationComponent.class); Vector3f spawnPosition = findSpawnPos(location.getWorldPosition(), entityHeight).get(); // TODO: Handle Optional being empty location.setWorldPosition(spawnPosition); controller.saveComponent(location); logger.debug("Spawing player at: {}", spawnPosition); builder.getComponent(LocationComponent.class).setWorldPosition(spawnPosition); builder.setOwner(controller); ClientComponent clientComp = controller.getComponent(ClientComponent.class); if (clientComp != null) { ColorComponent colorComp = clientComp.clientInfo.getComponent(ColorComponent.class); MeshComponent meshComp = builder.getComponent(MeshComponent.class); meshComp.color = colorComp.color; } CharacterComponent playerComponent = builder.getComponent(CharacterComponent.class); playerComponent.controller = controller; EntityRef player = builder.build(); Location.attachChild(player, controller, new Vector3f(), new Quat4f(0, 0, 0, 1)); return player; } private float getHeightOf(ComponentContainer prefab) { BoxShapeComponent box = prefab.getComponent(BoxShapeComponent.class); if (box != null) { return box.extents.getY(); } CylinderShapeComponent cylinder = prefab.getComponent(CylinderShapeComponent.class); if (cylinder != null) { return cylinder.height; } CapsuleShapeComponent capsule = prefab.getComponent(CapsuleShapeComponent.class); if (capsule != null) { return capsule.height; } SphereShapeComponent sphere = prefab.getComponent(SphereShapeComponent.class); if (sphere != null) { return sphere.radius * 2.0f; } HullShapeComponent hull = prefab.getComponent(HullShapeComponent.class); if (hull != null) { AABB aabb = hull.sourceMesh.getAABB(); return aabb.maxY() - aabb.minY(); } logger.warn("entity {} does not have any known extent specification - using default", prefab); return 1.0f; } private Optional<Vector3f> findSpawnPos(Vector3f targetPos, float entityHeight) { int targetBlockX = TeraMath.floorToInt(targetPos.x); int targetBlockY = TeraMath.floorToInt(targetPos.y); int targetBlockZ = TeraMath.floorToInt(targetPos.z); Vector2i center = new Vector2i(targetBlockX, targetBlockZ); for (BaseVector2i pos : SpiralIterable.clockwise(center).maxRadius(32).scale(2).build()) { Vector3i testPos = new Vector3i(pos.getX(), targetBlockY, pos.getY()); Vector3i spawnPos = findOpenVerticalPosition(testPos, entityHeight); if (spawnPos != null) { return Optional.of(new Vector3f(spawnPos.getX(), spawnPos.getY() + entityHeight, spawnPos.getZ())); } } return Optional.empty(); } /** * find a spot above the surface that is big enough for this character * @param spawnPos the position to check * @param height the height of the entity to spawn * @return the topmost solid block <code>null</code> if none was found */ private Vector3i findOpenVerticalPosition(Vector3i spawnPos, float height) { int consecutiveAirBlocks = 0; Vector3i newSpawnPos = new Vector3i(spawnPos); // TODO: also start looking downwards if initial spawn pos is in the air for (int i = 1; i < 20; i++) { if (worldProvider.isBlockRelevant(newSpawnPos)) { if (worldProvider.getBlock(newSpawnPos).isPenetrable()) { consecutiveAirBlocks++; } else { consecutiveAirBlocks = 0; } if (consecutiveAirBlocks >= height) { newSpawnPos.subY(consecutiveAirBlocks); return newSpawnPos; } newSpawnPos.add(0, 1, 0); } } return null; } }