637 lines
32 KiB
Java
637 lines
32 KiB
Java
package com.hypixel.hytale.builtin.instances;
|
|
|
|
import com.hypixel.hytale.assetstore.AssetPack;
|
|
import com.hypixel.hytale.builtin.blockphysics.WorldValidationUtil;
|
|
import com.hypixel.hytale.builtin.instances.blocks.ConfigurableInstanceBlock;
|
|
import com.hypixel.hytale.builtin.instances.blocks.InstanceBlock;
|
|
import com.hypixel.hytale.builtin.instances.command.InstancesCommand;
|
|
import com.hypixel.hytale.builtin.instances.config.ExitInstance;
|
|
import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig;
|
|
import com.hypixel.hytale.builtin.instances.config.InstanceEntityConfig;
|
|
import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig;
|
|
import com.hypixel.hytale.builtin.instances.config.WorldReturnPoint;
|
|
import com.hypixel.hytale.builtin.instances.event.DiscoverInstanceEvent;
|
|
import com.hypixel.hytale.builtin.instances.interactions.ExitInstanceInteraction;
|
|
import com.hypixel.hytale.builtin.instances.interactions.TeleportConfigInstanceInteraction;
|
|
import com.hypixel.hytale.builtin.instances.interactions.TeleportInstanceInteraction;
|
|
import com.hypixel.hytale.builtin.instances.page.ConfigureInstanceBlockPage;
|
|
import com.hypixel.hytale.builtin.instances.removal.IdleTimeoutCondition;
|
|
import com.hypixel.hytale.builtin.instances.removal.InstanceDataResource;
|
|
import com.hypixel.hytale.builtin.instances.removal.RemovalCondition;
|
|
import com.hypixel.hytale.builtin.instances.removal.RemovalSystem;
|
|
import com.hypixel.hytale.builtin.instances.removal.TimeoutCondition;
|
|
import com.hypixel.hytale.builtin.instances.removal.WorldEmptyCondition;
|
|
import com.hypixel.hytale.codec.schema.config.ObjectSchema;
|
|
import com.hypixel.hytale.codec.schema.config.Schema;
|
|
import com.hypixel.hytale.codec.schema.config.StringSchema;
|
|
import com.hypixel.hytale.common.util.FormatUtil;
|
|
import com.hypixel.hytale.component.ComponentAccessor;
|
|
import com.hypixel.hytale.component.ComponentRegistryProxy;
|
|
import com.hypixel.hytale.component.ComponentType;
|
|
import com.hypixel.hytale.component.Holder;
|
|
import com.hypixel.hytale.component.Ref;
|
|
import com.hypixel.hytale.component.ResourceType;
|
|
import com.hypixel.hytale.component.Store;
|
|
import com.hypixel.hytale.event.EventRegistry;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.math.vector.Transform;
|
|
import com.hypixel.hytale.math.vector.Vector3f;
|
|
import com.hypixel.hytale.protocol.GameMode;
|
|
import com.hypixel.hytale.protocol.SoundCategory;
|
|
import com.hypixel.hytale.server.core.Message;
|
|
import com.hypixel.hytale.server.core.Options;
|
|
import com.hypixel.hytale.server.core.asset.AssetModule;
|
|
import com.hypixel.hytale.server.core.asset.GenerateSchemaEvent;
|
|
import com.hypixel.hytale.server.core.asset.LoadAssetEvent;
|
|
import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController;
|
|
import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent;
|
|
import com.hypixel.hytale.server.core.entity.UUIDComponent;
|
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
|
import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData;
|
|
import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent;
|
|
import com.hypixel.hytale.server.core.event.events.player.DrainPlayerFromWorldEvent;
|
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
|
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
|
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
|
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
|
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
|
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction;
|
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
|
import com.hypixel.hytale.server.core.universe.Universe;
|
|
import com.hypixel.hytale.server.core.universe.world.SoundUtil;
|
|
import com.hypixel.hytale.server.core.universe.world.ValidationOption;
|
|
import com.hypixel.hytale.server.core.universe.world.World;
|
|
import com.hypixel.hytale.server.core.universe.world.WorldConfig;
|
|
import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.provider.EmptyChunkStorageProvider;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.provider.IChunkStorageProvider;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.provider.MigrationChunkStorageProvider;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.resources.EmptyResourceStorageProvider;
|
|
import com.hypixel.hytale.server.core.util.EventTitleUtil;
|
|
import com.hypixel.hytale.server.core.util.io.FileUtil;
|
|
import com.hypixel.hytale.sneakythrow.SneakyThrow;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.io.IOException;
|
|
import java.nio.file.FileVisitResult;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.SimpleFileVisitor;
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
|
import java.util.EnumSet;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.logging.Level;
|
|
import java.util.stream.Stream;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class InstancesPlugin extends JavaPlugin {
|
|
private static InstancesPlugin instance;
|
|
@Nonnull
|
|
public static final String INSTANCE_PREFIX = "instance-";
|
|
@Nonnull
|
|
public static final String CONFIG_FILENAME = "instance.bson";
|
|
private ResourceType<ChunkStore, InstanceDataResource> instanceDataResourceType;
|
|
private ComponentType<EntityStore, InstanceEntityConfig> instanceEntityConfigComponentType;
|
|
private ComponentType<ChunkStore, InstanceBlock> instanceBlockComponentType;
|
|
private ComponentType<ChunkStore, ConfigurableInstanceBlock> configurableInstanceBlockComponentType;
|
|
|
|
public static InstancesPlugin get() {
|
|
return instance;
|
|
}
|
|
|
|
public InstancesPlugin(@Nonnull JavaPluginInit init) {
|
|
super(init);
|
|
instance = this;
|
|
}
|
|
|
|
@Override
|
|
protected void setup() {
|
|
EventRegistry eventRegistry = this.getEventRegistry();
|
|
ComponentRegistryProxy<ChunkStore> chunkStoreRegistry = this.getChunkStoreRegistry();
|
|
this.getCommandRegistry().registerCommand(new InstancesCommand());
|
|
eventRegistry.register((short)64, LoadAssetEvent.class, this::validateInstanceAssets);
|
|
eventRegistry.register(GenerateSchemaEvent.class, InstancesPlugin::generateSchema);
|
|
eventRegistry.registerGlobal(AddPlayerToWorldEvent.class, InstancesPlugin::onPlayerAddToWorld);
|
|
eventRegistry.registerGlobal(DrainPlayerFromWorldEvent.class, InstancesPlugin::onPlayerDrainFromWorld);
|
|
eventRegistry.register(PlayerConnectEvent.class, InstancesPlugin::onPlayerConnect);
|
|
eventRegistry.registerGlobal(PlayerReadyEvent.class, InstancesPlugin::onPlayerReady);
|
|
this.instanceBlockComponentType = chunkStoreRegistry.registerComponent(InstanceBlock.class, "Instance", InstanceBlock.CODEC);
|
|
chunkStoreRegistry.registerSystem(new InstanceBlock.OnRemove());
|
|
this.configurableInstanceBlockComponentType = chunkStoreRegistry.registerComponent(
|
|
ConfigurableInstanceBlock.class, "InstanceConfig", ConfigurableInstanceBlock.CODEC
|
|
);
|
|
chunkStoreRegistry.registerSystem(new ConfigurableInstanceBlock.OnRemove());
|
|
this.instanceDataResourceType = chunkStoreRegistry.registerResource(InstanceDataResource.class, "InstanceData", InstanceDataResource.CODEC);
|
|
chunkStoreRegistry.registerSystem(new RemovalSystem());
|
|
this.instanceEntityConfigComponentType = this.getEntityStoreRegistry()
|
|
.registerComponent(InstanceEntityConfig.class, "Instance", InstanceEntityConfig.CODEC);
|
|
this.getCodecRegistry(RemovalCondition.CODEC)
|
|
.register("WorldEmpty", WorldEmptyCondition.class, WorldEmptyCondition.CODEC)
|
|
.register("IdleTimeout", IdleTimeoutCondition.class, IdleTimeoutCondition.CODEC)
|
|
.register("Timeout", TimeoutCondition.class, TimeoutCondition.CODEC);
|
|
this.getCodecRegistry(Interaction.CODEC)
|
|
.register("TeleportInstance", TeleportInstanceInteraction.class, TeleportInstanceInteraction.CODEC)
|
|
.register("TeleportConfigInstance", TeleportConfigInstanceInteraction.class, TeleportConfigInstanceInteraction.CODEC)
|
|
.register("ExitInstance", ExitInstanceInteraction.class, ExitInstanceInteraction.CODEC);
|
|
this.getCodecRegistry(RespawnController.CODEC).register("ExitInstance", ExitInstance.class, ExitInstance.CODEC);
|
|
OpenCustomUIInteraction.registerBlockEntityCustomPage(
|
|
this, ConfigureInstanceBlockPage.class, "ConfigInstanceBlock", ConfigureInstanceBlockPage::new, () -> {
|
|
Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
|
|
holder.ensureComponent(ConfigurableInstanceBlock.getComponentType());
|
|
return holder;
|
|
}
|
|
);
|
|
this.getCodecRegistry(WorldConfig.PLUGIN_CODEC).register(InstanceWorldConfig.class, "Instance", InstanceWorldConfig.CODEC);
|
|
}
|
|
|
|
@Nonnull
|
|
public CompletableFuture<World> spawnInstance(@Nonnull String name, @Nonnull World forWorld, @Nonnull Transform returnPoint) {
|
|
return this.spawnInstance(name, null, forWorld, returnPoint);
|
|
}
|
|
|
|
@Nonnull
|
|
public CompletableFuture<World> spawnInstance(@Nonnull String name, @Nullable String worldName, @Nonnull World forWorld, @Nonnull Transform returnPoint) {
|
|
Universe universe = Universe.get();
|
|
Path path = universe.getPath();
|
|
Path assetPath = getInstanceAssetPath(name);
|
|
UUID uuid = UUID.randomUUID();
|
|
String worldKey = worldName;
|
|
if (worldName == null) {
|
|
worldKey = "instance-" + safeName(name) + "-" + uuid;
|
|
}
|
|
|
|
Path worldPath = path.resolve("worlds").resolve(worldKey);
|
|
String finalWorldKey = worldKey;
|
|
return WorldConfig.load(assetPath.resolve("instance.bson"))
|
|
.thenApplyAsync(
|
|
SneakyThrow.sneakyFunction(
|
|
config -> {
|
|
config.setUuid(uuid);
|
|
config.setDisplayName(WorldConfig.formatDisplayName(name));
|
|
InstanceWorldConfig instanceConfig = InstanceWorldConfig.ensureAndGet(config);
|
|
instanceConfig.setReturnPoint(
|
|
new WorldReturnPoint(forWorld.getWorldConfig().getUuid(), returnPoint, instanceConfig.shouldPreventReconnection())
|
|
);
|
|
config.markChanged();
|
|
long start = System.nanoTime();
|
|
this.getLogger().at(Level.INFO).log("Copying instance files for %s to world %s", name, finalWorldKey);
|
|
|
|
try (Stream<Path> files = Files.walk(assetPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) {
|
|
files.forEach(SneakyThrow.sneakyConsumer(filePath -> {
|
|
Path rel = assetPath.relativize(filePath);
|
|
Path toPath = worldPath.resolve(rel.toString());
|
|
if (Files.isDirectory(filePath)) {
|
|
Files.createDirectories(toPath);
|
|
} else {
|
|
if (Files.isRegularFile(filePath)) {
|
|
Files.copy(filePath, toPath);
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
this.getLogger()
|
|
.at(Level.INFO)
|
|
.log("Completed instance files for %s to world %s in %s", name, finalWorldKey, FormatUtil.nanosToString(System.nanoTime() - start));
|
|
return config;
|
|
}
|
|
)
|
|
)
|
|
.thenCompose(config -> universe.makeWorld(finalWorldKey, worldPath, config));
|
|
}
|
|
|
|
public static void teleportPlayerToLoadingInstance(
|
|
@Nonnull Ref<EntityStore> entityRef,
|
|
@Nonnull ComponentAccessor<EntityStore> componentAccessor,
|
|
@Nonnull CompletableFuture<World> worldFuture,
|
|
@Nullable Transform overrideReturn
|
|
) {
|
|
World originalWorld = componentAccessor.getExternalData().getWorld();
|
|
TransformComponent transformComponent = componentAccessor.getComponent(entityRef, TransformComponent.getComponentType());
|
|
|
|
assert transformComponent != null;
|
|
|
|
Transform originalPosition = transformComponent.getTransform().clone();
|
|
InstanceEntityConfig instanceEntityConfigComponent = componentAccessor.getComponent(entityRef, InstanceEntityConfig.getComponentType());
|
|
if (instanceEntityConfigComponent == null) {
|
|
instanceEntityConfigComponent = componentAccessor.addComponent(entityRef, InstanceEntityConfig.getComponentType());
|
|
}
|
|
|
|
if (overrideReturn != null) {
|
|
instanceEntityConfigComponent.setReturnPointOverride(new WorldReturnPoint(originalWorld.getWorldConfig().getUuid(), overrideReturn, false));
|
|
} else {
|
|
instanceEntityConfigComponent.setReturnPointOverride(null);
|
|
}
|
|
|
|
PlayerRef playerRefComponent = componentAccessor.getComponent(entityRef, PlayerRef.getComponentType());
|
|
|
|
assert playerRefComponent != null;
|
|
|
|
originalWorld.execute(playerRefComponent::removeFromStore);
|
|
UUIDComponent uuidComponent = componentAccessor.getComponent(entityRef, UUIDComponent.getComponentType());
|
|
|
|
assert uuidComponent != null;
|
|
|
|
UUID playerUUID = uuidComponent.getUuid();
|
|
InstanceEntityConfig finalPlayerConfig = instanceEntityConfigComponent;
|
|
worldFuture.orTimeout(1L, TimeUnit.MINUTES).thenCompose(world -> {
|
|
ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider();
|
|
Transform spawnPoint = spawnProvider != null ? spawnProvider.getSpawnPoint(world, playerUUID) : null;
|
|
return world.addPlayer(playerRefComponent, spawnPoint, Boolean.TRUE, Boolean.FALSE);
|
|
}).whenComplete((ret, ex) -> {
|
|
if (ex != null) {
|
|
((HytaleLogger.Api)get().getLogger().at(Level.SEVERE).withCause(ex)).log("Failed to send %s to instance world", playerRefComponent.getUsername());
|
|
finalPlayerConfig.setReturnPointOverride(null);
|
|
}
|
|
|
|
if (ret == null) {
|
|
if (originalWorld.isAlive()) {
|
|
originalWorld.addPlayer(playerRefComponent, originalPosition, Boolean.TRUE, Boolean.FALSE);
|
|
} else {
|
|
World defaultWorld = Universe.get().getDefaultWorld();
|
|
if (defaultWorld != null) {
|
|
defaultWorld.addPlayer(playerRefComponent, null, Boolean.TRUE, Boolean.FALSE);
|
|
} else {
|
|
get().getLogger().at(Level.SEVERE).log("No fallback world for %s, disconnecting", playerRefComponent.getUsername());
|
|
playerRefComponent.getPacketHandler().disconnect("Failed to teleport - no world available");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public static void teleportPlayerToInstance(
|
|
@Nonnull Ref<EntityStore> playerRef,
|
|
@Nonnull ComponentAccessor<EntityStore> componentAccessor,
|
|
@Nonnull World targetWorld,
|
|
@Nullable Transform overrideReturn
|
|
) {
|
|
World originalWorld = componentAccessor.getExternalData().getWorld();
|
|
WorldConfig originalWorldConfig = originalWorld.getWorldConfig();
|
|
if (overrideReturn != null) {
|
|
InstanceEntityConfig instanceConfig = componentAccessor.ensureAndGetComponent(playerRef, InstanceEntityConfig.getComponentType());
|
|
instanceConfig.setReturnPointOverride(new WorldReturnPoint(originalWorldConfig.getUuid(), overrideReturn, false));
|
|
}
|
|
|
|
UUIDComponent uuidComponent = componentAccessor.getComponent(playerRef, UUIDComponent.getComponentType());
|
|
|
|
assert uuidComponent != null;
|
|
|
|
UUID playerUUID = uuidComponent.getUuid();
|
|
WorldConfig targetWorldConfig = targetWorld.getWorldConfig();
|
|
ISpawnProvider spawnProvider = targetWorldConfig.getSpawnProvider();
|
|
if (spawnProvider == null) {
|
|
throw new IllegalStateException("Spawn provider cannot be null when teleporting player to instance!");
|
|
} else {
|
|
HeadRotation headRotationComponent = componentAccessor.getComponent(playerRef, HeadRotation.getComponentType());
|
|
|
|
assert headRotationComponent != null;
|
|
|
|
Vector3f headRotation = headRotationComponent.getRotation();
|
|
Transform spawnPoint = spawnProvider.getSpawnPoint(targetWorld, playerUUID);
|
|
Vector3f spawnHeadRotation = headRotation.clone();
|
|
spawnHeadRotation.setYaw(spawnPoint.getRotation().getYaw());
|
|
componentAccessor.addComponent(playerRef, Teleport.getComponentType(), new Teleport(targetWorld, spawnPoint).withHeadRotation(spawnHeadRotation));
|
|
}
|
|
}
|
|
|
|
public static void exitInstance(@Nonnull Ref<EntityStore> targetRef, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
World world = componentAccessor.getExternalData().getWorld();
|
|
InstanceEntityConfig entityConfig = componentAccessor.getComponent(targetRef, InstanceEntityConfig.getComponentType());
|
|
WorldReturnPoint returnPoint = entityConfig != null ? entityConfig.getReturnPoint() : null;
|
|
if (returnPoint == null) {
|
|
WorldConfig config = world.getWorldConfig();
|
|
InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config);
|
|
returnPoint = instanceConfig != null ? instanceConfig.getReturnPoint() : null;
|
|
if (returnPoint == null) {
|
|
throw new IllegalArgumentException("Player is not in an instance");
|
|
}
|
|
}
|
|
|
|
Universe universe = Universe.get();
|
|
World targetWorld = universe.getWorld(returnPoint.getWorld());
|
|
if (targetWorld == null) {
|
|
throw new IllegalArgumentException("Missing return world");
|
|
} else {
|
|
Teleport teleportComponent = new Teleport(targetWorld, returnPoint.getReturnPoint());
|
|
componentAccessor.addComponent(targetRef, Teleport.getComponentType(), teleportComponent);
|
|
}
|
|
}
|
|
|
|
public static void safeRemoveInstance(@Nonnull String worldName) {
|
|
safeRemoveInstance(Universe.get().getWorld(worldName));
|
|
}
|
|
|
|
public static void safeRemoveInstance(@Nonnull UUID worldUUID) {
|
|
safeRemoveInstance(Universe.get().getWorld(worldUUID));
|
|
}
|
|
|
|
public static void safeRemoveInstance(@Nullable World instanceWorld) {
|
|
if (instanceWorld != null) {
|
|
Store<ChunkStore> chunkStore = instanceWorld.getChunkStore().getStore();
|
|
chunkStore.getResource(InstanceDataResource.getResourceType()).setHadPlayer(true);
|
|
WorldConfig config = instanceWorld.getWorldConfig();
|
|
InstanceWorldConfig instanceConfig = InstanceWorldConfig.get(config);
|
|
if (instanceConfig != null) {
|
|
instanceConfig.setRemovalConditions(WorldEmptyCondition.REMOVE_WHEN_EMPTY);
|
|
}
|
|
|
|
config.markChanged();
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public static Path getInstanceAssetPath(@Nonnull String name) {
|
|
for (AssetPack pack : AssetModule.get().getAssetPacks()) {
|
|
Path path = pack.getRoot().resolve("Server").resolve("Instances").resolve(name);
|
|
if (Files.exists(path)) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances").resolve(name);
|
|
}
|
|
|
|
public static boolean doesInstanceAssetExist(@Nonnull String name) {
|
|
return Files.exists(getInstanceAssetPath(name).resolve("instance.bson"));
|
|
}
|
|
|
|
@Nonnull
|
|
public static CompletableFuture<World> loadInstanceAssetForEdit(@Nonnull String name) {
|
|
Path path = getInstanceAssetPath(name);
|
|
Universe universe = Universe.get();
|
|
return WorldConfig.load(path.resolve("instance.bson")).thenCompose(config -> {
|
|
config.setUuid(UUID.randomUUID());
|
|
config.setSavingPlayers(false);
|
|
config.setIsAllNPCFrozen(true);
|
|
config.setTicking(false);
|
|
config.setGameMode(GameMode.Creative);
|
|
config.setDeleteOnRemove(false);
|
|
InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY);
|
|
config.markChanged();
|
|
String worldName = "instance-edit-" + safeName(name);
|
|
return universe.makeWorld(worldName, path, config);
|
|
});
|
|
}
|
|
|
|
@Nonnull
|
|
public List<String> getInstanceAssets() {
|
|
final List<String> instances = new ObjectArrayList();
|
|
|
|
for (AssetPack pack : AssetModule.get().getAssetPacks()) {
|
|
final Path path = pack.getRoot().resolve("Server").resolve("Instances");
|
|
if (Files.isDirectory(path)) {
|
|
try {
|
|
Files.walkFileTree(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
|
|
@Nonnull
|
|
public FileVisitResult preVisitDirectory(@Nonnull Path dir, @Nonnull BasicFileAttributes attrs) {
|
|
if (Files.exists(dir.resolve("instance.bson"))) {
|
|
Path relative = path.relativize(dir);
|
|
String name = relative.toString();
|
|
instances.add(name);
|
|
return FileVisitResult.SKIP_SUBTREE;
|
|
} else {
|
|
return FileVisitResult.CONTINUE;
|
|
}
|
|
}
|
|
});
|
|
} catch (IOException var6) {
|
|
throw SneakyThrow.sneakyThrow(var6);
|
|
}
|
|
}
|
|
}
|
|
|
|
return instances;
|
|
}
|
|
|
|
private static void onPlayerConnect(@Nonnull PlayerConnectEvent event) {
|
|
Holder<EntityStore> holder = event.getHolder();
|
|
Player playerComponent = holder.getComponent(Player.getComponentType());
|
|
|
|
assert playerComponent != null;
|
|
|
|
PlayerConfigData playerConfig = playerComponent.getPlayerConfigData();
|
|
InstanceEntityConfig config = InstanceEntityConfig.ensureAndGet(holder);
|
|
String lastWorldName = playerConfig.getWorld();
|
|
World lastWorld = Universe.get().getWorld(lastWorldName);
|
|
WorldReturnPoint fallbackWorld = config.getReturnPoint();
|
|
if (fallbackWorld != null && (lastWorld == null || fallbackWorld.isReturnOnReconnect())) {
|
|
lastWorld = Universe.get().getWorld(fallbackWorld.getWorld());
|
|
if (lastWorld != null) {
|
|
Transform transform = fallbackWorld.getReturnPoint();
|
|
TransformComponent transformComponent = holder.ensureAndGetComponent(TransformComponent.getComponentType());
|
|
transformComponent.setPosition(transform.getPosition());
|
|
Vector3f rotationClone = transformComponent.getRotation().clone();
|
|
rotationClone.setYaw(transform.getRotation().getYaw());
|
|
transformComponent.setRotation(rotationClone);
|
|
HeadRotation headRotationComponent = holder.ensureAndGetComponent(HeadRotation.getComponentType());
|
|
headRotationComponent.teleportRotation(transform.getRotation());
|
|
}
|
|
} else if (lastWorld != null) {
|
|
config.setReturnPointOverride(config.getReturnPoint());
|
|
}
|
|
}
|
|
|
|
private static void onPlayerAddToWorld(@Nonnull AddPlayerToWorldEvent event) {
|
|
Holder<EntityStore> holder = event.getHolder();
|
|
InstanceWorldConfig worldConfig = InstanceWorldConfig.get(event.getWorld().getWorldConfig());
|
|
if (worldConfig == null) {
|
|
InstanceEntityConfig entityConfig = holder.getComponent(InstanceEntityConfig.getComponentType());
|
|
if (entityConfig != null && entityConfig.getReturnPoint() != null) {
|
|
entityConfig.setReturnPoint(null);
|
|
}
|
|
} else {
|
|
InstanceEntityConfig entityConfig = InstanceEntityConfig.ensureAndGet(holder);
|
|
if (entityConfig.getReturnPointOverride() == null) {
|
|
entityConfig.setReturnPoint(worldConfig.getReturnPoint());
|
|
} else {
|
|
WorldReturnPoint override = entityConfig.getReturnPointOverride();
|
|
override.setReturnOnReconnect(worldConfig.shouldPreventReconnection());
|
|
entityConfig.setReturnPoint(override);
|
|
entityConfig.setReturnPointOverride(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void onPlayerReady(@Nonnull PlayerReadyEvent event) {
|
|
Player player = event.getPlayer();
|
|
World world = player.getWorld();
|
|
if (world != null) {
|
|
WorldConfig worldConfig = world.getWorldConfig();
|
|
InstanceWorldConfig instanceWorldConfig = InstanceWorldConfig.get(worldConfig);
|
|
if (instanceWorldConfig != null) {
|
|
InstanceDiscoveryConfig discoveryConfig = instanceWorldConfig.getDiscovery();
|
|
if (discoveryConfig != null) {
|
|
PlayerConfigData playerConfigData = player.getPlayerConfigData();
|
|
UUID instanceUuid = worldConfig.getUuid();
|
|
if (discoveryConfig.alwaysDisplay() || !playerConfigData.getDiscoveredInstances().contains(instanceUuid)) {
|
|
Set<UUID> discoveredInstances = new HashSet<>(playerConfigData.getDiscoveredInstances());
|
|
discoveredInstances.add(instanceUuid);
|
|
playerConfigData.setDiscoveredInstances(discoveredInstances);
|
|
Ref<EntityStore> playerRef = event.getPlayerRef();
|
|
if (playerRef.isValid()) {
|
|
world.execute(() -> {
|
|
Store<EntityStore> store = world.getEntityStore().getStore();
|
|
showInstanceDiscovery(playerRef, store, instanceUuid, discoveryConfig);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void showInstanceDiscovery(
|
|
@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UUID instanceUuid, @Nonnull InstanceDiscoveryConfig discoveryConfig
|
|
) {
|
|
DiscoverInstanceEvent.Display discoverInstanceEvent = new DiscoverInstanceEvent.Display(instanceUuid, discoveryConfig.clone());
|
|
store.invoke(ref, discoverInstanceEvent);
|
|
discoveryConfig = discoverInstanceEvent.getDiscoveryConfig();
|
|
if (!discoverInstanceEvent.isCancelled() && discoverInstanceEvent.shouldDisplay()) {
|
|
PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType());
|
|
if (playerRefComponent != null) {
|
|
String subtitleKey = discoveryConfig.getSubtitleKey();
|
|
Message subtitle = subtitleKey != null ? Message.translation(subtitleKey) : Message.empty();
|
|
EventTitleUtil.showEventTitleToPlayer(
|
|
playerRefComponent,
|
|
Message.translation(discoveryConfig.getTitleKey()),
|
|
subtitle,
|
|
discoveryConfig.isMajor(),
|
|
discoveryConfig.getIcon(),
|
|
discoveryConfig.getDuration(),
|
|
discoveryConfig.getFadeInDuration(),
|
|
discoveryConfig.getFadeOutDuration()
|
|
);
|
|
String discoverySoundEventId = discoveryConfig.getDiscoverySoundEventId();
|
|
if (discoverySoundEventId != null) {
|
|
int assetIndex = SoundEvent.getAssetMap().getIndex(discoverySoundEventId);
|
|
if (assetIndex != Integer.MIN_VALUE) {
|
|
SoundUtil.playSoundEvent2d(ref, assetIndex, SoundCategory.UI, store);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void onPlayerDrainFromWorld(@Nonnull DrainPlayerFromWorldEvent event) {
|
|
InstanceEntityConfig config = InstanceEntityConfig.removeAndGet(event.getHolder());
|
|
if (config != null) {
|
|
WorldReturnPoint returnPoint = config.getReturnPoint();
|
|
if (returnPoint != null) {
|
|
World returnWorld = Universe.get().getWorld(returnPoint.getWorld());
|
|
if (returnWorld != null) {
|
|
event.setWorld(returnWorld);
|
|
event.setTransform(returnPoint.getReturnPoint());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void generateSchema(@Nonnull GenerateSchemaEvent event) {
|
|
ObjectSchema worldConfig = WorldConfig.CODEC.toSchema(event.getContext());
|
|
Map<String, Schema> props = worldConfig.getProperties();
|
|
props.put("UUID", Schema.anyOf(new StringSchema(), new ObjectSchema()));
|
|
worldConfig.setTitle("Instance Configuration");
|
|
worldConfig.setId("InstanceConfig.json");
|
|
Schema.HytaleMetadata hytaleMetadata = worldConfig.getHytale();
|
|
if (hytaleMetadata != null) {
|
|
hytaleMetadata.setPath("Instances");
|
|
hytaleMetadata.setExtension("instance.bson");
|
|
hytaleMetadata.setUiEditorIgnore(Boolean.TRUE);
|
|
}
|
|
|
|
event.addSchema("InstanceConfig.json", worldConfig);
|
|
event.addSchemaLink("InstanceConfig", List.of("Instances/**/instance.bson"), ".bson");
|
|
}
|
|
|
|
private void validateInstanceAssets(@Nonnull LoadAssetEvent event) {
|
|
Path path = AssetModule.get().getBaseAssetPack().getRoot().resolve("Server").resolve("Instances");
|
|
if (Options.getOptionSet().has(Options.VALIDATE_ASSETS) && Files.isDirectory(path) && !event.isShouldShutdown()) {
|
|
StringBuilder errors = new StringBuilder();
|
|
|
|
for (String name : this.getInstanceAssets()) {
|
|
StringBuilder sb = new StringBuilder();
|
|
Path instancePath = getInstanceAssetPath(name);
|
|
Universe universe = Universe.get();
|
|
WorldConfig config = WorldConfig.load(instancePath.resolve("instance.bson")).join();
|
|
IChunkStorageProvider storage = config.getChunkStorageProvider();
|
|
config.setChunkStorageProvider(new MigrationChunkStorageProvider(new IChunkStorageProvider[]{storage}, EmptyChunkStorageProvider.INSTANCE));
|
|
config.setResourceStorageProvider(EmptyResourceStorageProvider.INSTANCE);
|
|
config.setUuid(UUID.randomUUID());
|
|
config.setSavingPlayers(false);
|
|
config.setIsAllNPCFrozen(true);
|
|
config.setSavingConfig(false);
|
|
config.setTicking(false);
|
|
config.setGameMode(GameMode.Creative);
|
|
config.setDeleteOnRemove(false);
|
|
config.setCompassUpdating(false);
|
|
InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY);
|
|
config.markChanged();
|
|
String worldName = "instance-validate-" + safeName(name);
|
|
|
|
try {
|
|
World world = universe.makeWorld(worldName, instancePath, config, false).join();
|
|
EnumSet<ValidationOption> options = EnumSet.of(ValidationOption.BLOCK_STATES, ValidationOption.BLOCKS);
|
|
world.validate(sb, WorldValidationUtil.blockValidator(errors, options), options);
|
|
} catch (Exception var18) {
|
|
sb.append("\t").append(var18.getMessage());
|
|
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(var18)).log("Failed to validate: " + name);
|
|
} finally {
|
|
if (!sb.isEmpty()) {
|
|
errors.append("Instance: ").append(name).append('\n').append((CharSequence)sb).append('\n');
|
|
}
|
|
}
|
|
|
|
if (universe.getWorld(worldName) != null) {
|
|
universe.removeWorld(worldName);
|
|
}
|
|
}
|
|
|
|
if (!errors.isEmpty()) {
|
|
this.getLogger().at(Level.SEVERE).log("Failed to validate instances:\n" + errors);
|
|
event.failed(true, "failed to validate instances");
|
|
}
|
|
|
|
HytaleLogger.getLogger()
|
|
.at(Level.INFO)
|
|
.log("Loading Instance assets phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()));
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public static String safeName(@Nonnull String name) {
|
|
return name.replace('/', '-');
|
|
}
|
|
|
|
@Nonnull
|
|
public ResourceType<ChunkStore, InstanceDataResource> getInstanceDataResourceType() {
|
|
return this.instanceDataResourceType;
|
|
}
|
|
|
|
@Nonnull
|
|
public ComponentType<EntityStore, InstanceEntityConfig> getInstanceEntityConfigComponentType() {
|
|
return this.instanceEntityConfigComponentType;
|
|
}
|
|
|
|
@Nonnull
|
|
public ComponentType<ChunkStore, InstanceBlock> getInstanceBlockComponentType() {
|
|
return this.instanceBlockComponentType;
|
|
}
|
|
|
|
@Nonnull
|
|
public ComponentType<ChunkStore, ConfigurableInstanceBlock> getConfigurableInstanceBlockComponentType() {
|
|
return this.configurableInstanceBlockComponentType;
|
|
}
|
|
}
|