package com.hypixel.hytale.builtin.portals.ui;
import com.hypixel.hytale.builtin.instances.InstancesPlugin;
import com.hypixel.hytale.builtin.instances.config.InstanceDiscoveryConfig;
import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig;
import com.hypixel.hytale.builtin.portals.PortalsPlugin;
import com.hypixel.hytale.builtin.portals.components.PortalDevice;
import com.hypixel.hytale.builtin.portals.components.PortalDeviceConfig;
import com.hypixel.hytale.builtin.portals.integrations.PortalGameplayConfig;
import com.hypixel.hytale.builtin.portals.integrations.PortalRemovalCondition;
import com.hypixel.hytale.builtin.portals.resources.PortalWorld;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.logger.HytaleLogger;
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.protocol.SoundCategory;
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.protocol.packets.interface_.Page;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import com.hypixel.hytale.server.core.asset.type.item.config.PortalKey;
import com.hypixel.hytale.server.core.asset.type.portalworld.PillTag;
import com.hypixel.hytale.server.core.asset.type.portalworld.PortalDescription;
import com.hypixel.hytale.server.core.asset.type.portalworld.PortalSpawn;
import com.hypixel.hytale.server.core.asset.type.portalworld.PortalType;
import com.hypixel.hytale.server.core.asset.util.ColorParseUtil;
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.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.server.core.ui.builder.EventData;
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.SoundUtil;
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.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider;
import com.hypixel.hytale.server.core.universe.world.spawn.IndividualSpawnProvider;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class PortalDeviceSummonPage extends InteractiveCustomUIPage<Data> {
private final PortalDeviceConfig config;
private final Ref<ChunkStore> blockRef;
private final ItemStack offeredItemStack;
private static final Transform DEFAULT_WORLDGEN_SPAWN = new Transform(0.0, 140.0, 0.0);
public PortalDeviceSummonPage(@Nonnull PlayerRef playerRef, PortalDeviceConfig config, Ref<ChunkStore> blockRef, ItemStack offeredItemStack) {
super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, PortalDeviceSummonPage.Data.CODEC);
this.config = config;
this.blockRef = blockRef;
this.offeredItemStack = offeredItemStack;
}
public void build(@Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store) {
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
State state = this.computeState(playerComponent, store);
if (state != PortalDeviceSummonPage.Error.INVALID_BLOCK) {
if (state instanceof CanSpawnPortal) {
CanSpawnPortal canSpawn = (CanSpawnPortal)state;
commandBuilder.append("Pages/PortalDeviceSummon.ui");
PortalKey portalKey = canSpawn.portalKey();
PortalType portalType = canSpawn.portalType();
PortalDescription portalDesc = portalType.getDescription();
String var10002 = portalDesc.getSplashImageFilename();
commandBuilder.set("#Artwork.Background", "Pages/Portals/" + var10002);
commandBuilder.set("#Title0.TextSpans", portalDesc.getDisplayName());
commandBuilder.set("#FlavorLabel.TextSpans", portalDesc.getFlavorText());
updateCustomPills(commandBuilder, portalType);
String[] portalTypeId = portalDesc.getObjectivesKeys();
String[] instanceId = portalDesc.getWisdomKeys();
commandBuilder.set("#Objectives.Visible", portalTypeId.length > 0);
commandBuilder.set("#Tips.Visible", instanceId.length > 0);
updateBulletList(commandBuilder, "#ObjectivesList", portalTypeId);
updateBulletList(commandBuilder, "#TipsList", instanceId);
PortalGameplayConfig gameplayConfig = (PortalGameplayConfig)portalType.getGameplayConfig().getPluginConfig().get(PortalGameplayConfig.class);
long totalTimeLimit = TimeUnit.SECONDS.toMinutes((long)portalKey.getTimeLimitSeconds());
if (portalType.isVoidInvasionEnabled()) {
long minutesBreach = TimeUnit.SECONDS.toMinutes((long)gameplayConfig.getVoidEvent().getDurationSeconds());
long exploMinutes = totalTimeLimit - minutesBreach;
commandBuilder.set("#ExplorationTimeText.TextSpans", Message.translation("server.customUI.portalDevice.minutesToExplore").param("time", exploMinutes));
commandBuilder.set("#BreachTimeBullet.Visible", true);
commandBuilder.set("#BreachTimeText.TextSpans", Message.translation("server.customUI.portalDevice.minutesVoidInvasion").param("time", minutesBreach));
} else {
commandBuilder.set("#ExplorationTimeText.TextSpans", Message.translation("server.customUI.portalDevice.durationMins").param("time", totalTimeLimit));
}
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#SummonButton", EventData.of("Action", "SummonActivated"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.MouseEntered, "#SummonButton", EventData.of("Action", "SummonMouseEntered"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.MouseExited, "#SummonButton", EventData.of("Action", "SummonMouseExited"), false);
} else {
commandBuilder.append("Pages/PortalDeviceError.ui");
if (state != PortalDeviceSummonPage.Error.NOTHING_OFFERED && state != PortalDeviceSummonPage.Error.NOT_A_PORTAL_KEY) {
if (state == PortalDeviceSummonPage.Error.PORTAL_INSIDE_PORTAL) {
commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.portalInsidePortal"));
} else if (state == PortalDeviceSummonPage.Error.MAX_ACTIVE_PORTALS) {
commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.maxFragments").param("max", 4));
} else if (state instanceof InstanceKeyNotFound) {
InstanceKeyNotFound var8 = (InstanceKeyNotFound)state;
InstanceKeyNotFound var10000 = var8;
try {
var26 = var10000.instanceId();
} catch (Throwable var21) {
throw new MatchException(var21.toString(), var21);
}
String instanceId = var26;
commandBuilder.set("#UsageErrorLabel.Text", "The instance id '" + instanceId + "' does not exist, this is a developer error with the portaltype.");
} else if (state instanceof PortalTypeNotFound) {
PortalTypeNotFound var10 = (PortalTypeNotFound)state;
PortalTypeNotFound var27 = var10;
try {
var28 = var27.portalTypeId();
} catch (Throwable var20) {
throw new MatchException(var20.toString(), var20);
}
String instanceId = var28;
commandBuilder.set("#UsageErrorLabel.Text", "The portaltype id '" + instanceId + "' does not exist, this is a developer error with the portal key.");
} else if (state == PortalDeviceSummonPage.Error.BOTCHED_GAMEPLAY_CONFIG) {
commandBuilder.set("#UsageErrorLabel.Text", "The gameplay config set on the PortalType set in the key does not have a Portal plugin configuration, this is a developer error.");
} else {
commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.unknownError").param("state", state.toString()));
}
} else {
commandBuilder.set("#UsageErrorTitle.Text", Message.translation("server.customUI.portalDevice.needPortalKey"));
commandBuilder.set("#UsageErrorLabel.Text", Message.translation("server.customUI.portalDevice.nothingHeld"));
}
}
}
}
private static void updateCustomPills(UICommandBuilder commandBuilder, PortalType portalType) {
List<PillTag> pills = portalType.getDescription().getPillTags();
for(int i = 0; i < pills.size(); ++i) {
PillTag pillTag = (PillTag)pills.get(i);
String child = "#Pills[" + i + "]";
commandBuilder.append("#Pills", "Pages/Portals/Pill.ui");
commandBuilder.set(child + ".Background.Color", ColorParseUtil.colorToHexString(pillTag.getColor()));
commandBuilder.set(child + " #Label.TextSpans", pillTag.getMessage());
}
}
private static void updateBulletList(UICommandBuilder commandBuilder, String selector, String[] messageKeys) {
for(int i = 0; i < messageKeys.length; ++i) {
String messageKey = messageKeys[i];
String child = selector + "[" + i + "]";
commandBuilder.append(selector, "Pages/Portals/BulletPoint.ui");
commandBuilder.set(child + " #Label.TextSpans", Message.translation(messageKey));
}
}
public static Message createDescription(PortalType portalType, int timeLimitSeconds) {
Message msg = Message.empty();
Message durationMsg = formatDurationCrudely(timeLimitSeconds);
msg.insert(Message.translation("server.customUI.portalDevice.timeLimit").param("limit", durationMsg.color("#f9cb13")));
return msg;
}
private static Message formatDurationCrudely(int seconds) {
if (seconds < 0) {
return Message.translation("server.customUI.portalDevice.durationUnlimited");
} else if (seconds >= 120) {
int minutes = seconds / 60;
return Message.translation("server.customUI.portalDevice.durationMinutes").param("duration", minutes);
} else {
return Message.translation("server.customUI.portalDevice.durationSeconds").param("duration", seconds);
}
}
public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull Data data) {
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
State state = this.computeState(playerComponent, store);
if (state instanceof CanSpawnPortal) {
CanSpawnPortal canSpawn = (CanSpawnPortal)state;
if ("SummonMouseEntered".equals(data.action)) {
UICommandBuilder commandBuilder = new UICommandBuilder();
commandBuilder.set("#Vignette.Visible", true);
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
} else if ("SummonMouseExited".equals(data.action)) {
UICommandBuilder commandBuilder = new UICommandBuilder();
commandBuilder.set("#Vignette.Visible", false);
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
} else {
playerComponent.getPageManager().setPage(ref, store, Page.None);
World originWorld = ((EntityStore)store.getExternalData()).getWorld();
int index = canSpawn.blockState().getIndex();
int x = ChunkUtil.xFromBlockInColumn(index);
int y = ChunkUtil.yFromBlockInColumn(index);
int z = ChunkUtil.zFromBlockInColumn(index);
WorldChunk worldChunk = canSpawn.worldChunk();
PortalKey portalKey = canSpawn.portalKey();
PortalDevice portalDevice = canSpawn.portalDevice();
BlockType blockType = worldChunk.getBlockType(x, y, z);
if (blockType == portalDevice.getBaseBlockType()) {
if (this.config.areBlockStatesValid(blockType)) {
int rotation = worldChunk.getRotationIndex(x, y, z);
BlockType spawningType = blockType.getBlockForState(this.config.getSpawningState());
BlockType onType = blockType.getBlockForState(this.config.getOnState());
BlockType offType = blockType.getBlockForState(this.config.getOffState());
int setting = 6;
worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(spawningType.getId()), spawningType, rotation, 0, 6);
double worldX = (double)ChunkUtil.worldCoordFromLocalCoord(worldChunk.getX(), x) + 0.5;
double worldY = (double)y + 0.5;
double worldZ = (double)ChunkUtil.worldCoordFromLocalCoord(worldChunk.getZ(), z) + 0.5;
if (spawningType.getInteractionSoundEventIndex() != 0) {
SoundUtil.playSoundEvent3d(spawningType.getInteractionSoundEventIndex(), SoundCategory.SFX, worldX, worldY, worldZ, store);
}
decrementItemInHand(playerComponent.getInventory(), 1);
Transform transform = new Transform((double)x + 0.5, (double)y + 1.0, (double)z + 0.5);
UUIDComponent uuidComponent = (UUIDComponent)store.getComponent(ref, UUIDComponent.getComponentType());
assert uuidComponent != null;
PortalType portalType = canSpawn.portalType;
UUID playerUUID = uuidComponent.getUuid();
PortalGameplayConfig gameplayConfig = canSpawn.portalGameplayConfig;
InstancesPlugin.get().spawnInstance(portalType.getInstanceId(), originWorld, transform).thenCompose((spawnedWorld) -> {
WorldConfig worldConfig = spawnedWorld.getWorldConfig();
worldConfig.setDeleteOnUniverseStart(true);
worldConfig.setDeleteOnRemove(true);
worldConfig.setGameplayConfig(portalType.getGameplayConfigId());
InstanceWorldConfig instanceConfig = InstanceWorldConfig.ensureAndGet(worldConfig);
if (instanceConfig.getDiscovery() == null) {
InstanceDiscoveryConfig discoveryConfig = new InstanceDiscoveryConfig();
discoveryConfig.setTitleKey(portalType.getDescription().getDisplayNameKey());
discoveryConfig.setSubtitleKey("server.portals.discoverySubtitle");
discoveryConfig.setDisplay(true);
discoveryConfig.setAlwaysDisplay(true);
instanceConfig.setDiscovery(discoveryConfig);
}
PortalRemovalCondition portalRemoval = new PortalRemovalCondition((double)portalKey.getTimeLimitSeconds());
instanceConfig.setRemovalConditions(portalRemoval);
PortalWorld portalWorld = (PortalWorld)spawnedWorld.getEntityStore().getStore().getResource(PortalWorld.getResourceType());
portalWorld.init(portalType, portalKey.getTimeLimitSeconds(), portalRemoval, gameplayConfig);
String returnBlockType = portalDevice.getConfig().getReturnBlock();
if (returnBlockType == null) {
throw new RuntimeException("Return block type on PortalDevice is misconfigured");
} else {
return spawnReturnPortal(spawnedWorld, portalWorld, playerUUID, returnBlockType);
}
}).thenAcceptAsync((spawnedWorld) -> {
portalDevice.setDestinationWorld(spawnedWorld);
worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(onType.getId()), onType, rotation, 0, 6);
}, originWorld).exceptionallyAsync((t) -> {
playerComponent.sendMessage(Message.translation("server.portals.device.internalErrorSpawning"));
((HytaleLogger.Api)HytaleLogger.getLogger().at(Level.SEVERE).withCause(t)).log("Error creating instance for Portal Device " + String.valueOf(portalKey), t);
worldChunk.setBlock(x, y, z, BlockType.getAssetMap().getIndex(offType.getId()), offType, rotation, 0, 6);
return null;
}, originWorld);
}
}
}
}
}
private static CompletableFuture<World> spawnReturnPortal(World world, PortalWorld portalWorld, UUID sampleUuid, String portalBlockType) {
PortalSpawn portalSpawn = portalWorld.getPortalType().getPortalSpawn();
return getSpawnTransform(world, sampleUuid, portalSpawn).thenCompose((spawnTransform) -> {
Vector3d spawnPoint = spawnTransform.getPosition();
return world.getChunkAsync(ChunkUtil.indexChunkFromBlock((int)spawnPoint.x, (int)spawnPoint.z)).thenAccept((chunk) -> {
for(int dy = 0; dy < 3; ++dy) {
for(int dx = -1; dx <= 1; ++dx) {
for(int dz = -1; dz <= 1; ++dz) {
chunk.setBlock((int)spawnPoint.x + dx, (int)spawnPoint.y + dy, (int)spawnPoint.z + dz, BlockType.EMPTY);
}
}
}
chunk.setBlock((int)spawnPoint.x, (int)spawnPoint.y, (int)spawnPoint.z, portalBlockType);
portalWorld.setSpawnPoint(spawnTransform);
world.getWorldConfig().setSpawnProvider(new IndividualSpawnProvider(spawnTransform));
HytaleLogger.Api var10000 = HytaleLogger.getLogger().at(Level.INFO);
String var10001 = world.getName();
var10000.log("Spawned return portal for " + var10001 + " at " + (int)spawnPoint.x + ", " + (int)spawnPoint.y + ", " + (int)spawnPoint.z);
}).thenApply((nothing) -> world);
});
}
private static CompletableFuture<Transform> getSpawnTransform(World world, UUID sampleUuid, @Nullable PortalSpawn portalSpawn) {
ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider();
if (spawnProvider == null) {
return CompletableFuture.completedFuture((Object)null);
} else {
Transform worldSpawnPoint = spawnProvider.getSpawnPoint(world, sampleUuid);
return DEFAULT_WORLDGEN_SPAWN.equals(worldSpawnPoint) && portalSpawn != null ? CompletableFuture.supplyAsync(() -> {
Transform computedSpawn = PortalSpawnFinder.computeSpawnTransform(world, portalSpawn);
return computedSpawn == null ? worldSpawnPoint : computedSpawn;
}, world) : CompletableFuture.completedFuture(worldSpawnPoint);
}
}
private State computeState(@Nonnull Player player, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
if (!this.blockRef.isValid()) {
return PortalDeviceSummonPage.Error.INVALID_BLOCK;
} else {
int activeFragments = PortalsPlugin.getInstance().countActiveFragments();
if (activeFragments >= 4) {
return PortalDeviceSummonPage.Error.MAX_ACTIVE_PORTALS;
} else {
Store<ChunkStore> chunkStore = this.blockRef.getStore();
BlockModule.BlockStateInfo blockStateInfo = (BlockModule.BlockStateInfo)chunkStore.getComponent(this.blockRef, BlockModule.BlockStateInfo.getComponentType());
PortalDevice portalDevice = (PortalDevice)chunkStore.getComponent(this.blockRef, PortalDevice.getComponentType());
if (blockStateInfo != null && portalDevice != null) {
Ref<ChunkStore> chunkRef = blockStateInfo.getChunkRef();
if (chunkRef != null && chunkRef.isValid()) {
WorldChunk worldChunk = (WorldChunk)chunkStore.getComponent(chunkRef, WorldChunk.getComponentType());
if (worldChunk == null) {
return PortalDeviceSummonPage.Error.INVALID_BLOCK;
} else {
World existingDestinationWorld = portalDevice.getDestinationWorld();
if (existingDestinationWorld != null) {
return PortalDeviceSummonPage.Error.INVALID_DESTINATION;
} else if (this.offeredItemStack == null) {
return PortalDeviceSummonPage.Error.NOTHING_OFFERED;
} else {
ItemStack inHand = player.getInventory().getItemInHand();
if (!this.offeredItemStack.equals(inHand)) {
return PortalDeviceSummonPage.Error.OFFERED_IS_NOT_HELD;
} else {
Item offeredItem = this.offeredItemStack.getItem();
PortalKey portalKey = offeredItem.getPortalKey();
if (portalKey == null) {
return PortalDeviceSummonPage.Error.NOT_A_PORTAL_KEY;
} else {
String portalTypeId = portalKey.getPortalTypeId();
PortalType portalType = (PortalType)PortalType.getAssetMap().getAsset(portalTypeId);
if (portalType == null) {
return new PortalTypeNotFound(portalTypeId);
} else {
String instanceId = portalType.getInstanceId();
InstancesPlugin.get();
boolean instanceExists = InstancesPlugin.doesInstanceAssetExist(instanceId);
if (!instanceExists) {
return new InstanceKeyNotFound(instanceId);
} else {
PortalWorld insidePortalWorld = (PortalWorld)componentAccessor.getResource(PortalWorld.getResourceType());
if (insidePortalWorld.exists()) {
return PortalDeviceSummonPage.Error.PORTAL_INSIDE_PORTAL;
} else {
String gameplayConfigId = portalType.getGameplayConfigId();
GameplayConfig gameplayConfig = (GameplayConfig)GameplayConfig.getAssetMap().getAsset(gameplayConfigId);
PortalGameplayConfig portalGameplayConfig = gameplayConfig == null ? null : (PortalGameplayConfig)gameplayConfig.getPluginConfig().get(PortalGameplayConfig.class);
return (State)(portalGameplayConfig == null ? PortalDeviceSummonPage.Error.BOTCHED_GAMEPLAY_CONFIG : new CanSpawnPortal(portalKey, portalType, worldChunk, blockStateInfo, portalDevice, portalGameplayConfig));
}
}
}
}
}
}
}
} else {
return PortalDeviceSummonPage.Error.INVALID_BLOCK;
}
} else {
return PortalDeviceSummonPage.Error.INVALID_BLOCK;
}
}
}
}
private static void decrementItemInHand(Inventory inventory, int amount) {
if (!inventory.usingToolsItem()) {
byte hotbarSlot = inventory.getActiveHotbarSlot();
if (hotbarSlot != -1) {
ItemContainer hotbar = inventory.getHotbar();
ItemStack inHand = hotbar.getItemStack((short)hotbarSlot);
if (inHand != null) {
hotbar.removeItemStackFromSlot((short)hotbarSlot, inHand, amount, false, true);
}
}
}
}
private static record CanSpawnPortal(PortalKey portalKey, PortalType portalType, WorldChunk worldChunk, BlockModule.BlockStateInfo blockState, PortalDevice portalDevice, PortalGameplayConfig portalGameplayConfig) implements State {
}
private static record PortalTypeNotFound(String portalTypeId) implements State {
}
private static record InstanceKeyNotFound(String instanceId) implements State {
}
private static enum Error implements State {
NOTHING_OFFERED,
OFFERED_IS_NOT_HELD,
NOT_A_PORTAL_KEY,
INVALID_BLOCK,
INVALID_DESTINATION,
PORTAL_INSIDE_PORTAL,
BOTCHED_GAMEPLAY_CONFIG,
MAX_ACTIVE_PORTALS;
private Error() {
}
}
protected static class Data {
private static final String KEY_ACTION = "Action";
public static final BuilderCodec<Data> CODEC;
private String action;
protected Data() {
}
static {
CODEC = ((BuilderCodec.Builder)BuilderCodec.builder(Data.class, Data::new).append(new KeyedCodec("Action", Codec.STRING), (entry, s) -> entry.action = s, (entry) -> entry.action).add()).build();
}
}
private sealed interface State permits PortalDeviceSummonPage.CanSpawnPortal, PortalDeviceSummonPage.Error, PortalDeviceSummonPage.InstanceKeyNotFound, PortalDeviceSummonPage.PortalTypeNotFound {
}
}