package com.hypixel.hytale.builtin.model.pages; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.common.util.StringCompareUtil; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.NonSerialized; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; 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.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.asset.type.model.config.Model; import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; 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.modules.entity.component.HeadRotation; import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; import com.hypixel.hytale.server.core.ui.Value; 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.storage.EntityStore; import com.hypixel.hytale.server.core.util.TargetUtil; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class ChangeModelPage extends InteractiveCustomUIPage { private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Common/TextButton.ui"; private static final Value BUTTON_LABEL_STYLE = Value.ref("Common/TextButton.ui", "LabelStyle"); private static final Value BUTTON_LABEL_STYLE_SELECTED = Value.ref("Common/TextButton.ui", "SelectedLabelStyle"); @Nonnull private String searchQuery = ""; private List models; @Nullable private String selectedModel; @Nullable private Ref modelPreview; private Vector3d position; private Vector3f rotation; private float scale = 1.0F; public ChangeModelPage(@Nonnull PlayerRef playerRef) { super(playerRef, CustomPageLifetime.CanDismiss, ChangeModelPage.PageEventData.CODEC); } @Override public void build( @Nonnull Ref ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store store ) { commandBuilder.append("Pages/ChangeModelPage.ui"); eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false); eventBuilder.addEventBinding( CustomUIEventBindingType.ValueChanged, "#Scale", new EventData().append("Type", "UpdateScale").append("@Scale", "#Scale.Value"), false ); eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ChangeModel", new EventData().append("Type", "ChangeModel"), false); eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ResetModel", new EventData().append("Type", "ResetModel"), false); this.buildModelList(ref, store, commandBuilder, eventBuilder); } public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, @Nonnull ChangeModelPage.PageEventData data) { if (data.searchQuery != null) { this.searchQuery = data.searchQuery.trim().toLowerCase(); UICommandBuilder commandBuilder = new UICommandBuilder(); UIEventBuilder eventBuilder = new UIEventBuilder(); this.buildModelList(ref, store, commandBuilder, eventBuilder); this.sendUpdate(commandBuilder, eventBuilder, false); } else { String var9 = data.type; switch (var9) { case "Select": if (data.model != null) { UICommandBuilder commandBuilder = new UICommandBuilder(); this.selectModel(ref, store, data.model, commandBuilder); this.sendUpdate(commandBuilder, null, false); } break; case "UpdateScale": this.scale = data.scale; if (this.modelPreview.isValid()) { store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(this.getModel(this.scale))); } break; case "ChangeModel": if (this.selectedModel != null) { if (this.modelPreview.isValid()) { store.removeEntity(this.modelPreview, RemoveReason.REMOVE); } Model model = this.getModel(this.scale); store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(model)); store.getComponent(ref, Player.getComponentType()).getPageManager().setPage(ref, store, Page.None); } break; case "ResetModel": PlayerSkinComponent skinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); if (skinComponent == null) { return; } PlayerSkinComponent playerSkinComponent = store.getComponent(ref, PlayerSkinComponent.getComponentType()); Model newModel = CosmeticsModule.get().createModel(playerSkinComponent.getPlayerSkin()); store.putComponent(ref, ModelComponent.getComponentType(), new ModelComponent(newModel)); playerSkinComponent.setNetworkOutdated(); } } } @Override public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) { if (this.modelPreview != null && this.modelPreview.isValid()) { store.removeEntity(this.modelPreview, RemoveReason.REMOVE); } } private void buildModelList( @Nonnull Ref ref, @Nonnull Store store, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder ) { commandBuilder.clear("#ModelList"); Set roleTemplateNames = ModelAsset.getAssetMap().getAssetMap().keySet(); if (!this.searchQuery.isEmpty()) { Object2IntMap map = new Object2IntOpenHashMap(roleTemplateNames.size()); for (String value : roleTemplateNames) { int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH); if (fuzzyDistance > 0) { map.put(value, fuzzyDistance); } } this.models = map.keySet().stream().sorted().sorted(Comparator.comparingInt(map::getInt).reversed()).limit(20L).collect(Collectors.toList()); } else { this.models = roleTemplateNames.stream().sorted().sorted(String::compareTo).collect(Collectors.toList()); } int i = 0; for (int bound = this.models.size(); i < bound; i++) { String id = this.models.get(i); String selector = "#ModelList[" + i + "]"; commandBuilder.append("#ModelList", "Common/TextButton.ui"); commandBuilder.set(selector + " #Button.Text", id); eventBuilder.addEventBinding( CustomUIEventBindingType.Activating, selector + " #Button", new EventData().append("Type", "Select").append("Model", id), false ); } if (!this.models.isEmpty()) { if (!this.models.contains(this.selectedModel)) { this.selectModel(ref, store, this.models.getFirst(), commandBuilder); } else if (this.selectedModel != null) { this.selectModel(ref, store, this.selectedModel, commandBuilder); } } } private void selectModel(@Nonnull Ref ref, @Nonnull Store store, @Nonnull String modelId, @Nonnull UICommandBuilder commandBuilder) { if (this.selectedModel != null && this.models.contains(this.selectedModel)) { commandBuilder.set("#ModelList[" + this.models.indexOf(this.selectedModel) + "] #Button.Style", BUTTON_LABEL_STYLE); } commandBuilder.set("#ModelList[" + this.models.indexOf(modelId) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED); commandBuilder.set("#ModelName.Text", modelId); this.selectedModel = modelId; if (this.modelPreview == null || !this.modelPreview.isValid()) { TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); assert transformComponent != null; HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); assert headRotationComponent != null; Vector3d playerPosition = transformComponent.getPosition(); Vector3f headRotation = headRotationComponent.getRotation(); Vector3d previewPosition = TargetUtil.getTargetLocation(ref, 8.0, store); if (previewPosition == null) { previewPosition = playerPosition.clone().add(Transform.getDirection(headRotation.getPitch(), headRotation.getYaw()).scale(4.0)); } Vector3d targetGround = TargetUtil.getTargetLocation( store.getExternalData().getWorld(), blockId -> blockId != 0, previewPosition.x, previewPosition.y, previewPosition.z, 0.0, -1.0, 0.0, 8.0 ); if (targetGround != null) { previewPosition = targetGround; } Vector3d relativePos = playerPosition.clone().subtract(previewPosition); relativePos.setY(0.0); Vector3f previewRotation = Vector3f.lookAt(relativePos); this.position = previewPosition; this.rotation = previewRotation; Holder holder = store.getRegistry().newHolder(); holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get()); holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(previewPosition, previewRotation)); holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(this.getModel(this.scale))); holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(previewRotation)); this.modelPreview = store.addEntity(holder, AddReason.SPAWN); } else if (this.modelPreview != null && this.modelPreview.isValid()) { store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(this.getModel(1.0F))); } } @Nullable private Model getModel(float scale) { ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.selectedModel); return Model.createScaledModel(modelAsset, scale); } public static class PageEventData { static final String KEY_MODEL = "Model"; static final String KEY_TYPE = "Type"; static final String KEY_SEARCH_QUERY = "@SearchQuery"; static final String KEY_SCALE = "@Scale"; public static final BuilderCodec CODEC = BuilderCodec.builder( ChangeModelPage.PageEventData.class, ChangeModelPage.PageEventData::new ) .append(new KeyedCodec<>("Model", Codec.STRING), (entry, s) -> entry.model = s, entry -> entry.model) .add() .append(new KeyedCodec<>("Type", Codec.STRING), (entry, s) -> entry.type = s, entry -> entry.type) .add() .append(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery) .add() .append(new KeyedCodec<>("@Scale", Codec.FLOAT), (entry, s) -> entry.scale = s, entry -> entry.scale) .add() .build(); private String model; private String type; private String searchQuery; private float scale; } }