package com.hypixel.hytale.server.npc.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.math.vector.Vector3i;
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.item.config.Item;
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.command.system.exceptions.GeneralCommandException;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.entity.entities.BlockEntity;
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.ItemStack;
import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent;
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.PersistentModel;
import com.hypixel.hytale.server.core.modules.entity.component.PropComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent;
import com.hypixel.hytale.server.core.modules.entity.item.PreventItemMerging;
import com.hypixel.hytale.server.core.modules.entity.item.PreventPickup;
import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId;
import com.hypixel.hytale.server.core.ui.ItemGridSlot;
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.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.TargetUtil;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.spawning.ISpawnableWithModel;
import com.hypixel.hytale.server.spawning.SpawningContext;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class EntitySpawnPage extends InteractiveCustomUIPage<EntitySpawnPageEventData> {
private static final String COMMON_TEXT_BUTTON_DOCUMENT = "Common/TextButton.ui";
private static final String COMMON_UI_DOCUMENT = "Common.ui";
private static final Value<String> BUTTON_LABEL_STYLE = Value.<String>ref("Common/TextButton.ui", "LabelStyle");
private static final Value<String> BUTTON_LABEL_STYLE_SELECTED = Value.<String>ref("Common/TextButton.ui", "SelectedLabelStyle");
private static final Value<String> TAB_STYLE_ACTIVE = Value.<String>ref("Common.ui", "DefaultTextButtonStyle");
private static final Value<String> TAB_STYLE_INACTIVE = Value.<String>ref("Common.ui", "SecondaryTextButtonStyle");
private static final String TAB_NPC = "NPC";
private static final String TAB_ITEMS = "Items";
private static final String TAB_MODEL = "Model";
private static final String KEY_SELECT_AN_ITEM = "server.customUI.entitySpawnPage.selectAnItem";
private static final String KEY_SELECT_AN_NPC = "server.customUI.entitySpawnPage.selectAnNpc";
private static final String KEY_SELECT_A_MODEL = "server.customUI.entitySpawnPage.selectAModel";
private static final int MAX_SPAWN_COUNT = 100;
private static final float BLOCK_ENTITY_BASE_SCALE = 2.0F;
private static final int LOOK_RAYCAST_DISTANCE = 4;
private static final int FALLBACK_RAYCAST_DOWN_DISTANCE = 3;
private static final double FALLBACK_RAYCAST_Y_OFFSET = 0.5;
@Nonnull
private String activeTab = "NPC";
@Nonnull
private String searchQuery = "";
private List<String> npcRoles;
@Nullable
private String selectedNpcRole;
private List<String> modelIds;
@Nullable
private String selectedModelId;
@Nullable
private String selectedItemId;
@Nullable
private Ref<EntityStore> modelPreview;
private Vector3d position;
private Vector3f rotation;
private float currentRotationOffset = 0.0F;
private float currentScale = 1.0F;
private float lastPreviewScale = 1.0F;
private long lastScaleUpdateTime = 0L;
public EntitySpawnPage(@Nonnull PlayerRef playerRef) {
super(playerRef, CustomPageLifetime.CanDismiss, EntitySpawnPage.EntitySpawnPageEventData.CODEC);
}
public void build(@Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store) {
commandBuilder.append("Pages/EntitySpawnPage.ui");
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@SearchQuery", "#SearchInput.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#RotationOffset", (new EventData()).append("Type", "UpdateRotationOffset").append("@RotationOffset", "#RotationOffset.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ScaleSlider", (new EventData()).append("Type", "UpdateScale").append("@Scale", "#ScaleSlider.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.MouseButtonReleased, "#ScaleSlider", (new EventData()).append("Type", "ScaleReleased").append("@Scale", "#ScaleSlider.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Spawn", (new EventData()).append("Type", "Spawn").append("@Count", "#Count.Value").append("@Scale", "#ScaleSlider.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#TabNPC", (new EventData()).append("Type", "TabSwitch").append("Tab", "NPC"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#TabItems", (new EventData()).append("Type", "TabSwitch").append("Tab", "Items"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#TabModel", (new EventData()).append("Type", "TabSwitch").append("Tab", "Model"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Dropped, "#ItemMaterialSlot", (new EventData()).append("Type", "SetItemMaterial"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ClearMaterial", (new EventData()).append("Type", "ClearMaterial"), false);
this.buildList(ref, store, commandBuilder, eventBuilder);
}
public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull EntitySpawnPageEventData data) {
if (data.searchQuery != null) {
this.searchQuery = data.searchQuery.trim().toLowerCase();
UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();
this.buildList(ref, store, commandBuilder, eventBuilder);
this.sendUpdate(commandBuilder, eventBuilder, false);
} else {
switch (data.type) {
case "TabSwitch":
if (data.tab != null && !data.tab.equals(this.activeTab)) {
this.activeTab = data.tab;
this.searchQuery = "";
this.clearPreview(store);
UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();
this.updateTabVisibility(commandBuilder);
commandBuilder.set("#SearchInput.Value", "");
this.buildList(ref, store, commandBuilder, eventBuilder);
this.sendUpdate(commandBuilder, eventBuilder, false);
}
break;
case "Select":
this.handleSelect(ref, store, data);
break;
case "SetItemMaterial":
this.handleSetItemMaterial(ref, store, data);
break;
case "ClearMaterial":
this.clearSelectedItem(ref, store);
break;
case "UpdateRotationOffset":
this.currentRotationOffset = (float)Math.toRadians((double)data.rotationOffset);
if (this.modelPreview != null && this.modelPreview.isValid()) {
TransformComponent transform = (TransformComponent)store.getComponent(this.modelPreview, TransformComponent.getComponentType());
transform.getRotation().setYaw(this.rotation.getYaw() + this.currentRotationOffset);
HeadRotation headRotation = (HeadRotation)store.getComponent(this.modelPreview, HeadRotation.getComponentType());
if (headRotation != null) {
headRotation.getRotation().setYaw(this.rotation.getYaw() + this.currentRotationOffset);
}
}
break;
case "UpdateScale":
if (data.scale != null && data.scale >= 0.1F) {
this.currentScale = data.scale;
UICommandBuilder commandBuilder = new UICommandBuilder();
commandBuilder.set("#ScaleValue.Text", String.format("%.1f", this.currentScale));
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
long now = System.currentTimeMillis();
if (this.modelPreview != null && this.modelPreview.isValid() && this.currentScale != this.lastPreviewScale && now - this.lastScaleUpdateTime >= 200L) {
this.lastScaleUpdateTime = now;
this.lastPreviewScale = this.currentScale;
this.updatePreviewScale(ref, store);
}
}
break;
case "ScaleReleased":
if (data.scale != null && data.scale >= 0.1F) {
this.currentScale = data.scale;
if ("Model".equals(this.activeTab) && this.selectedModelId != null) {
ModelAsset modelAsset = (ModelAsset)ModelAsset.getAssetMap().getAsset(this.selectedModelId);
if (modelAsset != null) {
UICommandBuilder commandBuilder = new UICommandBuilder();
this.createOrUpdatePreview(ref, store, commandBuilder, Model.createStaticScaledModel(modelAsset, this.currentScale));
}
} else if ("Items".equals(this.activeTab) && this.selectedItemId != null) {
Item item = (Item)Item.getAssetMap().getAsset(this.selectedItemId);
if (item != null) {
Model model = this.getItemModel(item);
if (model != null) {
UICommandBuilder commandBuilder = new UICommandBuilder();
this.createOrUpdatePreview(ref, store, commandBuilder, model);
} else if (item.hasBlockType()) {
this.createOrUpdateBlockPreview(ref, store, this.selectedItemId);
} else {
this.createOrUpdateItemPreview(ref, store, this.selectedItemId);
}
}
}
}
break;
case "Spawn":
this.handleSpawn(ref, store, data);
}
}
}
private void handleSelect(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull EntitySpawnPageEventData data) {
UICommandBuilder commandBuilder = new UICommandBuilder();
switch (this.activeTab) {
case "NPC":
if (data.npcRole != null) {
this.selectNPCRole(ref, store, data.npcRole, commandBuilder);
}
break;
case "Items":
if (data.itemId != null) {
this.selectItem(ref, store, data.itemId, commandBuilder);
}
break;
case "Model":
if (data.modelId != null) {
this.selectModel(ref, store, data.modelId, commandBuilder);
}
}
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
}
private void handleSetItemMaterial(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull EntitySpawnPageEventData data) {
if (data.itemStackId != null) {
UICommandBuilder commandBuilder = new UICommandBuilder();
this.selectItem(ref, store, data.itemStackId, commandBuilder);
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
}
}
private void clearSelectedItem(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
this.selectedItemId = null;
this.clearPreview(store);
UICommandBuilder commandBuilder = new UICommandBuilder();
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAnItem"));
commandBuilder.set("#ItemMaterialSlot.Slots", new ItemGridSlot[]{new ItemGridSlot()});
commandBuilder.set("#ClearMaterial.Visible", false);
commandBuilder.set("#DropIndicator.Visible", true);
this.sendUpdate(commandBuilder, (UIEventBuilder)null, false);
}
private void handleSpawn(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull EntitySpawnPageEventData data) {
switch (this.activeTab) {
case "NPC" -> this.spawnNPC(ref, store, data.count);
case "Items" -> this.spawnItem(ref, store, data.count);
case "Model" -> this.spawnModel(ref, store, data.count);
}
}
private void spawnNPC(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, int count) {
if (this.selectedNpcRole != null && this.position != null && this.rotation != null) {
if (count >= 1 && count <= 100) {
this.clearPreview(store);
Vector3f spawnRotation = this.rotation.clone();
spawnRotation.setYaw(this.rotation.getYaw() + this.currentRotationOffset);
for(int i = 0; i < count; ++i) {
NPCPlugin.get().spawnNPC(store, this.selectedNpcRole, (String)null, this.position, spawnRotation);
}
((Player)store.getComponent(ref, Player.getComponentType())).getPageManager().setPage(ref, store, Page.None);
this.playerRef.sendMessage(Message.translation(count == 1 ? "server.npc.spawn.spawnedOne" : "server.npc.spawn.spawnedMany").param("quantity", count).param("type", this.selectedNpcRole));
}
}
}
private void spawnModel(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, int count) {
if (this.selectedModelId != null && this.position != null && this.rotation != null) {
if (count >= 1 && count <= 100) {
this.clearPreview(store);
ModelAsset modelAsset = (ModelAsset)ModelAsset.getAssetMap().getAsset(this.selectedModelId);
if (modelAsset != null) {
Model model = Model.createStaticScaledModel(modelAsset, this.currentScale);
Vector3f spawnRotation = this.rotation.clone();
spawnRotation.setYaw(this.rotation.getYaw() + this.currentRotationOffset);
for(int i = 0; i < count; ++i) {
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, spawnRotation));
holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model));
holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(new Model.ModelReference(this.selectedModelId, this.currentScale, (Map)null, true)));
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(spawnRotation));
holder.addComponent(PropComponent.getComponentType(), PropComponent.get());
holder.ensureComponent(UUIDComponent.getComponentType());
store.addEntity(holder, AddReason.SPAWN);
}
((Player)store.getComponent(ref, Player.getComponentType())).getPageManager().setPage(ref, store, Page.None);
this.playerRef.sendMessage(Message.translation("server.customUI.entitySpawnPage.spawnedModel").param("quantity", count).param("model", this.selectedModelId));
}
}
}
}
public void onDismiss(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
this.clearPreview(store);
}
private void clearPreview(@Nonnull Store<EntityStore> store) {
if (this.modelPreview != null && this.modelPreview.isValid()) {
store.removeEntity(this.modelPreview, RemoveReason.REMOVE);
}
this.modelPreview = null;
}
private void updateTabVisibility(@Nonnull UICommandBuilder commandBuilder) {
commandBuilder.set("#NPCContent.Visible", this.activeTab.equals("NPC"));
commandBuilder.set("#ItemsContent.Visible", this.activeTab.equals("Items"));
commandBuilder.set("#ModelContent.Visible", this.activeTab.equals("Model"));
commandBuilder.set("#TabNPC.Style", this.activeTab.equals("NPC") ? TAB_STYLE_ACTIVE : TAB_STYLE_INACTIVE);
commandBuilder.set("#TabItems.Style", this.activeTab.equals("Items") ? TAB_STYLE_ACTIVE : TAB_STYLE_INACTIVE);
commandBuilder.set("#TabModel.Style", this.activeTab.equals("Model") ? TAB_STYLE_ACTIVE : TAB_STYLE_INACTIVE);
commandBuilder.set("#RotationGroup.Visible", this.activeTab.equals("Model") || this.activeTab.equals("NPC") || this.activeTab.equals("Items"));
commandBuilder.set("#ScaleGroup.Visible", this.activeTab.equals("Model") || this.activeTab.equals("Items"));
if (this.activeTab.equals("Model") || this.activeTab.equals("Items")) {
commandBuilder.set("#ScaleSlider.Value", this.currentScale);
commandBuilder.set("#ScaleValue.Text", String.format("%.1f", this.currentScale));
}
}
private void buildList(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
this.updateTabVisibility(commandBuilder);
switch (this.activeTab) {
case "NPC" -> this.buildNPCList(ref, store, commandBuilder, eventBuilder);
case "Items" -> this.buildItemsContent(ref, store, commandBuilder);
case "Model" -> this.buildModelList(ref, store, commandBuilder, eventBuilder);
}
}
private void buildNPCList(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
commandBuilder.clear("#NPCList");
List<String> roleTemplateNames = NPCPlugin.get().getRoleTemplateNames(true);
if (!this.searchQuery.isEmpty()) {
Object2IntMap<String> map = new Object2IntOpenHashMap<String>(roleTemplateNames.size());
for(String value : roleTemplateNames) {
int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH);
if (fuzzyDistance > 0) {
map.put(value, fuzzyDistance);
}
}
Stream var10001 = map.keySet().stream().sorted();
Objects.requireNonNull(map);
this.npcRoles = (List)var10001.sorted(Comparator.comparingInt(map::getInt).reversed()).limit(20L).collect(Collectors.toList());
} else {
roleTemplateNames.sort(String::compareTo);
this.npcRoles = roleTemplateNames;
}
int i = 0;
for(int bound = this.npcRoles.size(); i < bound; ++i) {
String id = (String)this.npcRoles.get(i);
String selector = "#NPCList[" + i + "]";
commandBuilder.append("#NPCList", "Common/TextButton.ui");
commandBuilder.set(selector + " #Button.Text", id);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, selector + " #Button", (new EventData()).append("Type", "Select").append("NPCRole", id), false);
}
if (!this.npcRoles.isEmpty() && this.selectedNpcRole != null) {
if (this.npcRoles.contains(this.selectedNpcRole)) {
this.selectNPCRole(ref, store, this.selectedNpcRole, commandBuilder);
} else {
this.selectedNpcRole = null;
this.clearPreview(store);
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAnNpc"));
}
} else if (this.selectedNpcRole == null) {
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAnNpc"));
}
}
private void buildModelList(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
commandBuilder.clear("#ModelList");
Set<String> itemAndBlockModelPaths = new HashSet();
for(Item item : Item.getAssetMap().getAssetMap().values()) {
if (item.getModel() != null) {
itemAndBlockModelPaths.add(item.getModel());
}
}
for(BlockType blockType : BlockType.getAssetMap().getAssetMap().values()) {
if (blockType.getCustomModel() != null) {
itemAndBlockModelPaths.add(blockType.getCustomModel());
}
}
Set<String> allModels = new HashSet();
for(Map.Entry<String, ModelAsset> entry : ModelAsset.getAssetMap().getAssetMap().entrySet()) {
String modelPath = ((ModelAsset)entry.getValue()).getModel();
if (modelPath != null && !itemAndBlockModelPaths.contains(modelPath) && !modelPath.startsWith("Items/Projectiles/")) {
allModels.add((String)entry.getKey());
}
}
if (!this.searchQuery.isEmpty()) {
Object2IntMap<String> map = new Object2IntOpenHashMap<String>(allModels.size());
for(String value : allModels) {
int fuzzyDistance = StringCompareUtil.getFuzzyDistance(value, this.searchQuery, Locale.ENGLISH);
if (fuzzyDistance > 0) {
map.put(value, fuzzyDistance);
}
}
Stream var10001 = map.keySet().stream().sorted();
Objects.requireNonNull(map);
this.modelIds = (List)var10001.sorted(Comparator.comparingInt(map::getInt).reversed()).limit(20L).collect(Collectors.toList());
} else {
this.modelIds = (List)allModels.stream().sorted().collect(Collectors.toList());
}
int i = 0;
for(int bound = this.modelIds.size(); i < bound; ++i) {
String id = (String)this.modelIds.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("ModelId", id), false);
}
if (!this.modelIds.isEmpty() && this.selectedModelId != null) {
if (this.modelIds.contains(this.selectedModelId)) {
this.selectModel(ref, store, this.selectedModelId, commandBuilder);
} else {
this.selectedModelId = null;
this.clearPreview(store);
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAModel"));
}
} else if (this.selectedModelId == null) {
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAModel"));
}
}
private void buildItemsContent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UICommandBuilder commandBuilder) {
if (this.selectedItemId != null) {
Item item = (Item)Item.getAssetMap().getAsset(this.selectedItemId);
if (item != null) {
commandBuilder.set("#SelectedName.Text", this.selectedItemId);
commandBuilder.set("#ItemMaterialSlot.Slots", new ItemGridSlot[]{new ItemGridSlot(new ItemStack(this.selectedItemId, 1))});
commandBuilder.set("#ClearMaterial.Visible", true);
commandBuilder.set("#DropIndicator.Visible", false);
}
} else {
commandBuilder.set("#SelectedName.Text", Message.translation("server.customUI.entitySpawnPage.selectAnItem"));
commandBuilder.set("#ItemMaterialSlot.Slots", new ItemGridSlot[]{new ItemGridSlot()});
commandBuilder.set("#ClearMaterial.Visible", false);
commandBuilder.set("#DropIndicator.Visible", true);
}
}
private void selectItem(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull String itemId, @Nonnull UICommandBuilder commandBuilder) {
Item item = (Item)Item.getAssetMap().getAsset(itemId);
if (item != null) {
this.selectedItemId = itemId;
commandBuilder.set("#SelectedName.Text", itemId);
commandBuilder.set("#ItemMaterialSlot.Slots", new ItemGridSlot[]{new ItemGridSlot(new ItemStack(itemId, 1))});
commandBuilder.set("#ClearMaterial.Visible", true);
commandBuilder.set("#DropIndicator.Visible", false);
Model model = this.getItemModel(item);
if (model != null) {
this.createOrUpdatePreview(ref, store, commandBuilder, model);
} else if (item.hasBlockType()) {
this.createOrUpdateBlockPreview(ref, store, itemId);
} else {
this.createOrUpdateItemPreview(ref, store, itemId);
}
}
}
private void spawnItem(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, int count) {
if (this.selectedItemId != null && this.position != null && this.rotation != null) {
if (count >= 1 && count <= 100) {
Item item = (Item)Item.getAssetMap().getAsset(this.selectedItemId);
if (item != null) {
this.clearPreview(store);
Vector3f spawnRotation = this.rotation.clone();
spawnRotation.setYaw(this.rotation.getYaw() + this.currentRotationOffset);
Model model = this.getItemModel(item);
if (model != null) {
String modelId = this.getItemModelId(item);
for(int i = 0; i < count; ++i) {
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, spawnRotation));
holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model));
holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(new Model.ModelReference(modelId, this.currentScale, (Map)null, true)));
ItemStack itemStack = new ItemStack(this.selectedItemId, 1);
itemStack.setOverrideDroppedItemAnimation(true);
holder.addComponent(ItemComponent.getComponentType(), new ItemComponent(itemStack));
holder.addComponent(PreventPickup.getComponentType(), PreventPickup.INSTANCE);
holder.addComponent(PreventItemMerging.getComponentType(), PreventItemMerging.INSTANCE);
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(spawnRotation));
holder.addComponent(PropComponent.getComponentType(), PropComponent.get());
holder.ensureComponent(UUIDComponent.getComponentType());
store.addEntity(holder, AddReason.SPAWN);
}
} else if (item.hasBlockType()) {
for(int i = 0; i < count; ++i) {
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(BlockEntity.getComponentType(), new BlockEntity(this.selectedItemId));
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, spawnRotation));
holder.addComponent(EntityScaleComponent.getComponentType(), new EntityScaleComponent(this.currentScale * 2.0F));
ItemStack itemStack = new ItemStack(this.selectedItemId, 1);
itemStack.setOverrideDroppedItemAnimation(true);
holder.addComponent(ItemComponent.getComponentType(), new ItemComponent(itemStack));
holder.addComponent(PreventPickup.getComponentType(), PreventPickup.INSTANCE);
holder.addComponent(PreventItemMerging.getComponentType(), PreventItemMerging.INSTANCE);
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(spawnRotation));
holder.addComponent(PropComponent.getComponentType(), PropComponent.get());
holder.ensureComponent(UUIDComponent.getComponentType());
store.addEntity(holder, AddReason.SPAWN);
}
} else {
for(int i = 0; i < count; ++i) {
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, spawnRotation));
ItemStack itemStack = new ItemStack(this.selectedItemId, 1);
itemStack.setOverrideDroppedItemAnimation(true);
holder.addComponent(ItemComponent.getComponentType(), new ItemComponent(itemStack));
holder.addComponent(EntityScaleComponent.getComponentType(), new EntityScaleComponent(this.currentScale));
holder.addComponent(PreventPickup.getComponentType(), PreventPickup.INSTANCE);
holder.addComponent(PreventItemMerging.getComponentType(), PreventItemMerging.INSTANCE);
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(spawnRotation));
holder.addComponent(PropComponent.getComponentType(), PropComponent.get());
store.addEntity(holder, AddReason.SPAWN);
}
}
((Player)store.getComponent(ref, Player.getComponentType())).getPageManager().setPage(ref, store, Page.None);
this.playerRef.sendMessage(Message.translation("server.customUI.entitySpawnPage.spawnedItem").param("quantity", count).param("item", this.selectedItemId));
}
}
}
}
@Nullable
private String getItemModelId(@Nonnull Item item) {
String modelId = item.getModel();
if (modelId == null && item.hasBlockType()) {
BlockType blockType = (BlockType)BlockType.getAssetMap().getAsset(item.getId());
if (blockType != null && blockType.getCustomModel() != null) {
modelId = blockType.getCustomModel();
}
}
return modelId;
}
@Nullable
private Model getItemModel(@Nonnull Item item) {
String modelId = this.getItemModelId(item);
if (modelId == null) {
return null;
} else {
ModelAsset modelAsset = (ModelAsset)ModelAsset.getAssetMap().getAsset(modelId);
return modelAsset != null ? Model.createStaticScaledModel(modelAsset, this.currentScale) : null;
}
}
private void selectNPCRole(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull String npcRole, @Nonnull UICommandBuilder commandBuilder) {
if (this.selectedNpcRole != null && this.npcRoles.contains(this.selectedNpcRole)) {
commandBuilder.set("#NPCList[" + this.npcRoles.indexOf(this.selectedNpcRole) + "] #Button.Style", BUTTON_LABEL_STYLE);
}
commandBuilder.set("#NPCList[" + this.npcRoles.indexOf(npcRole) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED);
commandBuilder.set("#SelectedName.Text", npcRole);
this.selectedNpcRole = npcRole;
this.createOrUpdatePreview(ref, store, commandBuilder, this.getNPCModel());
}
private void selectModel(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull String modelId, @Nonnull UICommandBuilder commandBuilder) {
if (this.selectedModelId != null && this.modelIds.contains(this.selectedModelId)) {
commandBuilder.set("#ModelList[" + this.modelIds.indexOf(this.selectedModelId) + "] #Button.Style", BUTTON_LABEL_STYLE);
}
commandBuilder.set("#ModelList[" + this.modelIds.indexOf(modelId) + "] #Button.Style", BUTTON_LABEL_STYLE_SELECTED);
commandBuilder.set("#SelectedName.Text", modelId);
this.selectedModelId = modelId;
ModelAsset modelAsset = (ModelAsset)ModelAsset.getAssetMap().getAsset(modelId);
if (modelAsset != null) {
this.createOrUpdatePreview(ref, store, commandBuilder, Model.createStaticScaledModel(modelAsset, this.currentScale));
}
}
private void initPosition(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
TransformComponent transformComponent = (TransformComponent)store.getComponent(ref, TransformComponent.getComponentType());
assert transformComponent != null;
HeadRotation headRotationComponent = (HeadRotation)store.getComponent(ref, HeadRotation.getComponentType());
assert headRotationComponent != null;
Vector3d playerPosition = transformComponent.getPosition();
Vector3f headRotation = headRotationComponent.getRotation();
Vector3d direction = Transform.getDirection(headRotation.getPitch(), headRotation.getYaw());
Vector3d lookTarget = TargetUtil.getTargetLocation(ref, 4.0, store);
Vector3d previewPosition;
if (lookTarget != null) {
previewPosition = lookTarget;
} else {
Vector3d aheadPosition = playerPosition.clone().add(direction.clone().scale(4.0));
World world = ((EntityStore)store.getExternalData()).getWorld();
Vector3i groundTarget = TargetUtil.getTargetBlock(world, (blockId, fluidId) -> blockId != 0, aheadPosition.x, aheadPosition.y + 0.5, aheadPosition.z, 0.0, -1.0, 0.0, 3.0);
if (groundTarget != null) {
previewPosition = new Vector3d((double)groundTarget.x + 0.5, (double)(groundTarget.y + 1), (double)groundTarget.z + 0.5);
} else {
previewPosition = aheadPosition;
}
}
Vector3d relativePos = playerPosition.clone().subtract(previewPosition);
relativePos.setY(0.0);
Vector3f previewRotation = Vector3f.lookAt(relativePos);
this.position = previewPosition;
this.rotation = previewRotation;
this.currentRotationOffset = 0.0F;
}
private void createOrUpdatePreview(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull UICommandBuilder commandBuilder, @Nullable Model model) {
if (model != null) {
if (this.modelPreview != null && this.modelPreview.isValid()) {
store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(model));
} else {
this.initPosition(ref, store);
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get());
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, this.rotation));
holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(model));
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(this.rotation));
this.modelPreview = store.addEntity(holder, AddReason.SPAWN);
this.lastPreviewScale = this.currentScale;
}
}
}
private void updatePreviewScale(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
if (this.modelPreview != null && this.modelPreview.isValid()) {
EntityScaleComponent existingScale = (EntityScaleComponent)store.getComponent(this.modelPreview, EntityScaleComponent.getComponentType());
if (existingScale != null) {
boolean hasBlock = store.getComponent(this.modelPreview, BlockEntity.getComponentType()) != null;
existingScale.setScale(hasBlock ? this.currentScale * 2.0F : this.currentScale);
} else if ("Model".equals(this.activeTab) && this.selectedModelId != null) {
ModelAsset modelAsset = (ModelAsset)ModelAsset.getAssetMap().getAsset(this.selectedModelId);
if (modelAsset != null) {
store.putComponent(this.modelPreview, ModelComponent.getComponentType(), new ModelComponent(Model.createStaticScaledModel(modelAsset, this.currentScale)));
}
}
}
}
private void createOrUpdateBlockPreview(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull String blockTypeKey) {
this.clearPreview(store);
this.initPosition(ref, store);
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get());
holder.addComponent(BlockEntity.getComponentType(), new BlockEntity(blockTypeKey));
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, this.rotation));
holder.addComponent(EntityScaleComponent.getComponentType(), new EntityScaleComponent(this.currentScale * 2.0F));
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(this.rotation));
this.modelPreview = store.addEntity(holder, AddReason.SPAWN);
this.lastPreviewScale = this.currentScale;
}
private void createOrUpdateItemPreview(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull String itemId) {
this.clearPreview(store);
this.initPosition(ref, store);
Holder<EntityStore> holder = store.getRegistry().newHolder();
holder.addComponent(NetworkId.getComponentType(), new NetworkId(((EntityStore)store.getExternalData()).takeNextNetworkId()));
holder.addComponent(EntityStore.REGISTRY.getNonSerializedComponentType(), NonSerialized.get());
ItemStack itemStack = new ItemStack(itemId, 1);
itemStack.setOverrideDroppedItemAnimation(true);
holder.addComponent(ItemComponent.getComponentType(), new ItemComponent(itemStack));
holder.addComponent(EntityScaleComponent.getComponentType(), new EntityScaleComponent(this.currentScale));
holder.addComponent(PreventPickup.getComponentType(), PreventPickup.INSTANCE);
holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(this.position, this.rotation));
holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(this.rotation));
this.modelPreview = store.addEntity(holder, AddReason.SPAWN);
this.lastPreviewScale = this.currentScale;
}
@Nullable
private Model getNPCModel() {
NPCPlugin npcPlugin = NPCPlugin.get();
int roleIndex = npcPlugin.getIndex(this.selectedNpcRole);
npcPlugin.forceValidation(roleIndex);
BuilderInfo roleBuilderInfo = npcPlugin.getRoleBuilderInfo(roleIndex);
if (roleBuilderInfo == null) {
throw new IllegalStateException("Can't find a matching role builder");
} else if (!npcPlugin.testAndValidateRole(roleBuilderInfo)) {
throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.validation_failed"));
} else {
Builder<Role> roleBuilder = npcPlugin.tryGetCachedValidRole(roleIndex);
if (roleBuilder == null) {
throw new IllegalArgumentException("Can't find a matching role builder");
} else if (roleBuilder instanceof ISpawnableWithModel) {
ISpawnableWithModel spawnable = (ISpawnableWithModel)roleBuilder;
if (!roleBuilder.isSpawnable()) {
throw new IllegalArgumentException("Abstract role templates cannot be spawned directly - a variant needs to be created!");
} else {
SpawningContext spawningContext = new SpawningContext();
if (!spawningContext.setSpawnable(spawnable)) {
throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSetRolebuilder"));
} else {
return spawningContext.getModel();
}
}
} else {
throw new IllegalArgumentException("Role builder must support ISpawnableWithModel interface");
}
}
}
public static class EntitySpawnPageEventData {
static final String KEY_NPC_ROLE = "NPCRole";
static final String KEY_MODEL_ID = "ModelId";
static final String KEY_ITEM_ID = "ItemId";
static final String KEY_ITEM_STACK_ID = "ItemStackId";
static final String KEY_TYPE = "Type";
static final String KEY_TAB = "Tab";
static final String KEY_SEARCH_QUERY = "@SearchQuery";
static final String KEY_COUNT = "@Count";
static final String KEY_ROTATION_OFFSET = "@RotationOffset";
static final String KEY_SCALE = "@Scale";
public static final BuilderCodec<EntitySpawnPageEventData> CODEC;
private String npcRole;
private String modelId;
private String itemId;
private String itemStackId;
private String type;
private String tab;
private String searchQuery;
private int count;
private float rotationOffset;
private Float scale;
public EntitySpawnPageEventData() {
}
static {
CODEC = ((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)BuilderCodec.builder(EntitySpawnPageEventData.class, EntitySpawnPageEventData::new).append(new KeyedCodec("NPCRole", Codec.STRING), (entry, s) -> entry.npcRole = s, (entry) -> entry.npcRole).add()).append(new KeyedCodec("ModelId", Codec.STRING), (entry, s) -> entry.modelId = s, (entry) -> entry.modelId).add()).append(new KeyedCodec("ItemId", Codec.STRING), (entry, s) -> entry.itemId = s, (entry) -> entry.itemId).add()).append(new KeyedCodec("ItemStackId", Codec.STRING), (entry, s) -> entry.itemStackId = s, (entry) -> entry.itemStackId).add()).append(new KeyedCodec("Type", Codec.STRING), (entry, s) -> entry.type = s, (entry) -> entry.type).add()).append(new KeyedCodec("Tab", Codec.STRING), (entry, s) -> entry.tab = s, (entry) -> entry.tab).add()).append(new KeyedCodec("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, (entry) -> entry.searchQuery).add()).append(new KeyedCodec("@Count", Codec.INTEGER), (entry, s) -> entry.count = s, (entry) -> entry.count).add()).append(new KeyedCodec("@RotationOffset", Codec.FLOAT), (entry, s) -> entry.rotationOffset = s, (entry) -> entry.rotationOffset).add()).append(new KeyedCodec("@Scale", Codec.FLOAT), (entry, s) -> entry.scale = s, (entry) -> entry.scale).add()).build();
}
}
}