hytale-server/com/hypixel/hytale/builtin/instances/interactions/TeleportInstanceInteraction...

322 lines
16 KiB
Java

package com.hypixel.hytale.builtin.instances.interactions;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.builtin.instances.InstanceValidator;
import com.hypixel.hytale.builtin.instances.InstancesPlugin;
import com.hypixel.hytale.builtin.instances.blocks.InstanceBlock;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.codecs.EnumCodec;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.Axis;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.protocol.BlockPosition;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.protocol.WaitForDataFrom;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.teleport.PendingTeleport;
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class TeleportInstanceInteraction extends SimpleInstantInteraction {
public static final BuilderCodec<TeleportInstanceInteraction> CODEC = BuilderCodec.builder(
TeleportInstanceInteraction.class, TeleportInstanceInteraction::new, SimpleInstantInteraction.CODEC
)
.documentation("Teleports the **Player** to the named instance, creating it if required.")
.<String>appendInherited(
new KeyedCodec<>("InstanceName", Codec.STRING), (o, i) -> o.instanceName = i, o -> o.instanceName, (o, p) -> o.instanceName = p.instanceName
)
.documentation("The name of the **instance** to teleport to.")
.addValidator(Validators.nonNull())
.addValidator(InstanceValidator.INSTANCE)
.add()
.<String>appendInherited(
new KeyedCodec<>("InstanceKey", Codec.STRING), (o, i) -> o.instanceKey = i, o -> o.instanceKey, (o, p) -> o.instanceKey = p.instanceKey
)
.documentation("The key to name the world. Random if not provided")
.add()
.<Vector3d>appendInherited(
new KeyedCodec<>("PositionOffset", Vector3d.CODEC),
(o, i) -> o.positionOffset = i,
o -> o.positionOffset,
(o, p) -> o.positionOffset = p.positionOffset
)
.documentation("The offset to apply to the return point.\n\nUsed to prevent repeated interactions when returning from the instance.")
.add()
.<Vector3f>appendInherited(new KeyedCodec<>("Rotation", Vector3f.ROTATION), (o, i) -> o.rotation = i, o -> o.rotation, (o, p) -> o.rotation = p.rotation)
.documentation("The rotation to set the player to when returning from an instance.")
.add()
.<TeleportInstanceInteraction.OriginSource>appendInherited(
new KeyedCodec<>("OriginSource", TeleportInstanceInteraction.OriginSource.CODEC),
(o, i) -> o.originSource = i,
o -> o.originSource,
(o, p) -> o.originSource = p.originSource
)
.documentation("The source to use for the return position.\n\nDefaults to the player's position.")
.addValidator(Validators.nonNull())
.add()
.<Boolean>appendInherited(
new KeyedCodec<>("PersonalReturnPoint", Codec.BOOLEAN),
(o, i) -> o.personalReturnPoint = i,
o -> o.personalReturnPoint,
(o, p) -> o.personalReturnPoint = p.personalReturnPoint
)
.documentation(
"Whether the player entering the instance will have their own return point\nset to the current location. Overriding the world's return point."
)
.add()
.<Boolean>appendInherited(
new KeyedCodec<>("CloseOnBlockRemove", Codec.BOOLEAN),
(o, i) -> o.closeOnBlockRemove = i,
o -> o.closeOnBlockRemove,
(o, p) -> o.closeOnBlockRemove = p.closeOnBlockRemove
)
.documentation("Whether to delete the instance when the portal block is removed.")
.add()
.<Double>appendInherited(
new KeyedCodec<>("RemoveBlockAfter", Codec.DOUBLE),
(o, i) -> o.removeBlockAfter = i,
o -> o.removeBlockAfter,
(o, p) -> o.removeBlockAfter = p.removeBlockAfter
)
.documentation(
"The number of seconds to wait before removing the block that triggered\nthe interaction. A negative value disables this.\n\nThis is needed instead of using another interaction due to all interactions\nbeing stopped once teleporting to another world."
)
.add()
.afterDecode(i -> {
if (i.rotation != null) {
i.rotation.scale((float) (Math.PI / 180.0));
}
})
.build();
private static final int SET_BLOCK_SETTINGS = 256;
private String instanceName;
private String instanceKey;
private Vector3d positionOffset;
private Vector3f rotation;
@Nonnull
private TeleportInstanceInteraction.OriginSource originSource = TeleportInstanceInteraction.OriginSource.PLAYER;
private boolean personalReturnPoint = false;
private boolean closeOnBlockRemove = true;
private double removeBlockAfter = -1.0;
@Nonnull
@Override
public WaitForDataFrom getWaitForDataFrom() {
return WaitForDataFrom.Server;
}
@Override
protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) {
CommandBuffer<EntityStore> commandBuffer = context.getCommandBuffer();
Ref<EntityStore> ref = context.getEntity();
Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType());
if (playerComponent != null && !playerComponent.isWaitingForClientReady()) {
Archetype<EntityStore> archetype = commandBuffer.getArchetype(ref);
if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) {
World world = commandBuffer.getExternalData().getWorld();
InstancesPlugin module = InstancesPlugin.get();
Universe universe = Universe.get();
CompletableFuture<World> targetWorldFuture = null;
Transform returnPoint = null;
World targetWorld;
if (this.instanceKey != null) {
targetWorld = universe.getWorld(this.instanceKey);
if (targetWorld == null) {
returnPoint = this.makeReturnPoint(ref, context, commandBuffer);
targetWorldFuture = module.spawnInstance(this.instanceName, this.instanceKey, world, returnPoint);
}
} else {
BlockPosition targetBlock = context.getTargetBlock();
if (targetBlock == null) {
return;
}
ChunkStore chunkStore = world.getChunkStore();
Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z));
if (chunkRef == null || !chunkRef.isValid()) {
return;
}
BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType());
assert blockComponentChunk != null;
int index = ChunkUtil.indexBlockInColumn(targetBlock.x, targetBlock.y, targetBlock.z);
Ref<ChunkStore> blockRef = blockComponentChunk.getEntityReference(index);
InstanceBlock instanceState;
if (blockRef == null) {
Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
instanceState = holder.ensureAndGetComponent(InstanceBlock.getComponentType());
holder.addComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, chunkRef));
blockRef = chunkStore.getStore().addEntity(holder, AddReason.SPAWN);
instanceState.setCloseOnRemove(this.closeOnBlockRemove);
} else {
instanceState = chunkStore.getStore().getComponent(chunkRef, InstanceBlock.getComponentType());
}
if (blockRef == null) {
return;
}
if (instanceState == null) {
instanceState = chunkStore.getStore().ensureAndGetComponent(blockRef, InstanceBlock.getComponentType());
instanceState.setCloseOnRemove(this.closeOnBlockRemove);
}
UUID worldName = instanceState.getWorldUUID();
targetWorldFuture = instanceState.getWorldFuture();
targetWorld = worldName != null ? universe.getWorld(worldName) : null;
if (targetWorld == null && targetWorldFuture == null) {
returnPoint = this.makeReturnPoint(ref, context, commandBuffer);
targetWorldFuture = module.spawnInstance(this.instanceName, world, returnPoint);
instanceState.setWorldFuture(targetWorldFuture);
Ref<ChunkStore> finalBlockRef = blockRef;
InstanceBlock finalInstanceState = instanceState;
targetWorldFuture.thenAccept(instanceWorld -> {
if (finalBlockRef.isValid()) {
finalInstanceState.setWorldFuture(null);
finalInstanceState.setWorldUUID(instanceWorld.getWorldConfig().getUuid());
blockComponentChunk.markNeedsSaving();
}
});
}
}
if (targetWorldFuture != null) {
Transform personalReturnPoint = this.getPersonalReturnPoint(ref, context, returnPoint, commandBuffer);
InstancesPlugin.teleportPlayerToLoadingInstance(ref, commandBuffer, targetWorldFuture, personalReturnPoint);
} else if (targetWorld != null) {
Transform personalReturnPoint = this.getPersonalReturnPoint(ref, context, returnPoint, commandBuffer);
InstancesPlugin.teleportPlayerToInstance(ref, commandBuffer, targetWorld, personalReturnPoint);
}
if (this.removeBlockAfter >= 0.0) {
BlockPosition targetBlockx = context.getTargetBlock();
if (targetBlockx != null) {
if (this.removeBlockAfter == 0.0) {
world.getChunk(ChunkUtil.indexChunkFromBlock(targetBlockx.x, targetBlockx.z))
.setBlock(targetBlockx.x, targetBlockx.y, targetBlockx.z, 0, 256);
} else {
int block = world.getBlock(targetBlockx.x, targetBlockx.y, targetBlockx.z);
new CompletableFuture()
.completeOnTimeout(null, (long)(this.removeBlockAfter * 1.0E9), TimeUnit.NANOSECONDS)
.thenRunAsync(
() -> {
if (world.getBlock(targetBlock.x, targetBlock.y, targetBlock.z) == block) {
world.getChunk(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z))
.setBlock(targetBlock.x, targetBlock.y, targetBlock.z, 0, 256);
}
},
world
);
}
}
}
}
}
}
@Nullable
private Transform getPersonalReturnPoint(
@Nonnull Ref<EntityStore> playerRef,
@Nonnull InteractionContext context,
@Nullable Transform returnPoint,
@Nonnull ComponentAccessor<EntityStore> componentAccessor
) {
if (!this.personalReturnPoint) {
return null;
} else {
return returnPoint == null ? this.makeReturnPoint(playerRef, context, componentAccessor) : returnPoint;
}
}
@Nonnull
private Transform makeReturnPoint(
@Nonnull Ref<EntityStore> playerRef, @Nonnull InteractionContext context, @Nonnull ComponentAccessor<EntityStore> componentAccessor
) {
Transform transform = null;
switch (this.originSource) {
case PLAYER:
TransformComponent transformComponent = componentAccessor.getComponent(playerRef, TransformComponent.getComponentType());
assert transformComponent != null;
transform = transformComponent.getTransform().clone();
transform.getPosition().add(this.positionOffset);
transform.setRotation(this.rotation != null ? this.rotation : Vector3f.NaN);
break;
case BLOCK:
BlockPosition targetBlock = context.getTargetBlock();
if (targetBlock == null) {
throw new IllegalArgumentException("Can't use OriginSource.BLOCK without a target block");
}
World world = componentAccessor.getExternalData().getWorld();
WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(targetBlock.x, targetBlock.z));
if (chunk == null) {
throw new IllegalArgumentException("Missing chunk");
}
BlockType blockType = chunk.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z);
int rotationIndex = chunk.getRotationIndex(targetBlock.x, targetBlock.y, targetBlock.z);
RotationTuple rotationTuple = RotationTuple.get(rotationIndex);
IndexedLookupTableAssetMap<String, BlockBoundingBoxes> hitboxAssetMap = BlockBoundingBoxes.getAssetMap();
Box hitbox = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotationIndex).getBoundingBox();
Vector3d position = this.positionOffset != null ? rotationTuple.rotate(this.positionOffset) : new Vector3d();
position.x = position.x + (hitbox.middleX() + targetBlock.x);
position.y = position.y + (hitbox.middleY() + targetBlock.y);
position.z = position.z + (hitbox.middleZ() + targetBlock.z);
Vector3f rotation = Vector3f.NaN;
if (this.rotation != null) {
rotation = this.rotation.clone();
rotation.addRotationOnAxis(Axis.Y, rotationTuple.yaw().getDegrees());
rotation.addRotationOnAxis(Axis.X, rotationTuple.pitch().getDegrees());
}
transform = new Transform(position, rotation);
}
return transform;
}
private static enum OriginSource {
PLAYER,
BLOCK;
@Nonnull
public static EnumCodec<TeleportInstanceInteraction.OriginSource> CODEC = new EnumCodec<>(TeleportInstanceInteraction.OriginSource.class)
.documentKey(PLAYER, "The origin of operations will be based on the player's current position.")
.documentKey(BLOCK, "The origin of operations will be based on the middle of the block's hitbox.");
}
}