package playercommands; import java.awt.Color; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import com.aionemu.gameserver.dataholders.DataManager; import com.aionemu.gameserver.model.Gender; import com.aionemu.gameserver.model.gameobjects.Item; import com.aionemu.gameserver.model.gameobjects.player.Player; import com.aionemu.gameserver.model.items.ItemSlot; import com.aionemu.gameserver.model.templates.item.ItemTemplate; import com.aionemu.gameserver.model.templates.item.enums.EquipType; import com.aionemu.gameserver.model.templates.item.enums.ItemGroup; import com.aionemu.gameserver.model.templates.itemset.ItemPart; import com.aionemu.gameserver.model.templates.itemset.ItemSetTemplate; import com.aionemu.gameserver.network.aion.serverpackets.SM_CUSTOM_SETTINGS; import com.aionemu.gameserver.network.aion.serverpackets.SM_RIDE_ROBOT; import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE; import com.aionemu.gameserver.network.aion.serverpackets.SM_UPDATE_PLAYER_APPEARANCE; import com.aionemu.gameserver.utils.ChatUtil; import com.aionemu.gameserver.utils.PacketSendUtility; import com.aionemu.gameserver.utils.ThreadPoolManager; import com.aionemu.gameserver.utils.chathandlers.PlayerCommand; /** * @author Neon */ public class Preview extends PlayerCommand { private static final Map> PREVIEW_RESETS = new ConcurrentHashMap<>(); private static final int PREVIEW_TIME_SECONDS = 10; public Preview() { super("preview", "Previews equipment."); // @formatter:off setSyntaxInfo( " - Previews your equipped items in the specified color (dye item, color name or color HEX code).", " [color] - Previews the specified item(s) on your character (default: standard item color, optional: dye item, color name or color HEX code).", "Multiple items may be separated with commas, but not with spaces.", "If a single item is given and it's a part of an item set, you will get a preview of the whole item set." ); // @formatter:on } @Override public void execute(Player player, String... params) { if (player.getLevel() < 10) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_CHANGE_ITEM_SKIN_PC_LEVEL_LIMIT()); return; } if (params.length == 0) { sendInfo(player); return; } int i = 0; boolean onlyColor = params.length == 1 && !params[0].contains("[") && !params[0].matches("([1-9][0-9]{8},?)+"); List items = new ArrayList<>(); if (onlyColor) player.getEquipment().getEquippedForAppearance().forEach(item -> items.add(item.getItemTemplate())); else if (!parseItems(player, params[i++], items)) return; Integer itemColor = null; // null = default item color String colorText = "default"; if (params.length > i) { // try to get itemId of a dyeing item String colorParam = params[i]; itemColor = ChatUtil.getItemId(colorParam); ItemTemplate dyeItemTemplate = DataManager.ITEM_DATA.getItemTemplate(itemColor); if (itemColor != 0 && dyeItemTemplate != null && dyeItemTemplate.getActions() != null && dyeItemTemplate.getActions().getDyeAction() != null) { // itemColor is a dyeing item ID itemColor = dyeItemTemplate.getActions().getDyeAction().getColor(); colorText = ChatUtil.item(itemColor); } else { try { try { // try to get color by name itemColor = ((Color) Class.forName("java.awt.Color").getField(colorParam.toUpperCase()).get(null)).getRGB(); } catch (Exception e) { // try to get color by hex code if (colorParam.length() <= 8) { if (colorParam.startsWith("#")) colorParam = colorParam.substring(1); else if (colorParam.startsWith("0x") || colorParam.startsWith("0X")) colorParam = colorParam.substring(2); } itemColor = Integer.valueOf(colorParam, 16); } colorText = ChatUtil.color("#" + String.format("%06X", itemColor & 0xFFFFFF), itemColor); } catch (NumberFormatException e) { sendInfo(player, colorParam + " is not a valid color."); return; } } if (items.stream().noneMatch(ItemTemplate::isItemDyePermitted)) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_ITEM_COLOR_CHANGE_ERROR_CANNOTDYE(items.get(0).getL10n())); return; } } String itemNames = ""; long previewItemsSlotMask = 0; List previewItems = new ArrayList<>(); if (items.size() == 1 && items.get(0).isItemSet()) { // preview whole set ItemSetTemplate itemSet = items.get(0).getItemSet(); for (ItemPart part : itemSet.getItempart()) previewItemsSlotMask |= addFakeItem(previewItems, previewItemsSlotMask, part.getItemId(), itemColor); } else { for (ItemTemplate template : items) previewItemsSlotMask |= addFakeItem(previewItems, previewItemsSlotMask, template.getTemplateId(), itemColor); } int previewRobotId = 0; for (Item previewItem : previewItems) { itemNames += "\n\t" + ChatUtil.item(previewItem.getItemId()); if (player.isInRobotMode() && previewItem.getItemTemplate().getItemGroup() == ItemGroup.KEYBLADE) previewRobotId = previewItem.getItemTemplate().getRobotId(); } addOwnEquipment(player, previewItems, previewItemsSlotMask); previewItems.sort(Comparator.comparingLong(Item::getEquipmentSlot)); // order by equipment slot ids (ascending) to avoid display bugs int display = player.getPlayerSettings().getDisplay() | SM_CUSTOM_SETTINGS.HIDE_LEGION_CLOAK; if (previewItems.stream().anyMatch(item -> item.getEquipmentSlot() == ItemSlot.HELMET.getSlotIdMask())) { display &= ~SM_CUSTOM_SETTINGS.HIDE_HELMET; } if (previewItems.stream().anyMatch(item -> item.getEquipmentSlot() == ItemSlot.PLUME.getSlotIdMask())) { display &= ~SM_CUSTOM_SETTINGS.HIDE_PLUME; } PacketSendUtility.sendPacket(player, new SM_CUSTOM_SETTINGS(player.getObjectId(), 1, display, player.getPlayerSettings().getDeny())); PacketSendUtility.sendPacket(player, new SM_UPDATE_PLAYER_APPEARANCE(player.getObjectId(), previewItems)); int switchRobotAnimationSeconds = 0; if (previewRobotId != 0) { switchRobotAnimationSeconds = 1; updateRobotAppearance(player, previewRobotId); } schedulePreviewReset(player, PREVIEW_TIME_SECONDS + switchRobotAnimationSeconds, previewRobotId != 0); if (onlyColor) sendInfo(player, "Previewing your equipment for " + PREVIEW_TIME_SECONDS + " seconds in color " + colorText); else sendInfo(player, "Previewing the following items for " + PREVIEW_TIME_SECONDS + " seconds (color: " + colorText + "):" + itemNames); } private boolean parseItems(Player player, String param, List items) { String[] ids = param.split(",|(?<=[^,])(?=\\[)|(?<=[\\]])(?=[^\\[])"); // split on , and between item tags (square brackets) for (String id : ids) { int itemId = ChatUtil.getItemId(id); if (itemId == 0) { sendInfo(player, "Invalid item specified."); return false; } ItemTemplate itemTemplate = DataManager.ITEM_DATA.getItemTemplate(itemId); if (itemTemplate == null) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_CHANGE_ITEM_SKIN_NO_TARGET_ITEM()); return false; } else if (itemTemplate.getEquipmentType() == EquipType.NONE) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_CHANGE_ITEM_SKIN_PREVIEW_INVALID_COSMETIC()); return false; } else if (itemTemplate.getRace() == player.getOppositeRace()) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_PREVIEW_INVALID_RACE()); return false; } else { Gender itemGender = itemTemplate.getUseLimits().getGenderPermitted(); if (itemGender != null && itemGender != player.getGender()) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_PREVIEW_INVALID_GENDER()); return false; } } if (!ItemSlot.isVisible(itemTemplate.getItemSlot())) { sendInfo(player, "Item is no visible equipment."); return false; } items.add(itemTemplate); } return true; } /** * @return Equipment slot mask of the preview item, 0 if it was not added */ private static long addFakeItem(List items, long previewItemsSlotMask, int itemId, Integer itemColor) { ItemTemplate itemTemplate = DataManager.ITEM_DATA.getItemTemplate(itemId); long itemSlotMask = itemTemplate.getItemSlot(); if (!ItemSlot.isVisible(itemSlotMask)) // don't add invisible items (like rings or belts) return 0; long occupiedSlots = previewItemsSlotMask & itemSlotMask; if (occupiedSlots == itemSlotMask) // an item of that kind is already present in the list return 0; if (itemSlotMask == ItemSlot.EARRING_RIGHT_OR_LEFT.getSlotIdMask()) { if (occupiedSlots == 0) itemSlotMask = ItemSlot.EARRINGS_LEFT.getSlotIdMask(); else if (occupiedSlots == ItemSlot.EARRINGS_LEFT.getSlotIdMask()) itemSlotMask = ItemSlot.EARRINGS_RIGHT.getSlotIdMask(); } Item previewItem = new Item(0, itemTemplate, 1, true, itemSlotMask); // ObjId 0 to avoid allocating new IDFactory IDs (it'll not be used anywhere) if (itemTemplate.isItemDyePermitted()) previewItem.setItemColor(itemColor); items.add(previewItem); return itemSlotMask; } private static void addOwnEquipment(Player player, List previewItems, long previewItemsSlotMask) { boolean previewTwoHanded = false; for (Item visibleEquipment : player.getEquipment().getEquippedForAppearance()) { if (previewTwoHanded || ItemSlot.isTwoHandedWeapon(visibleEquipment.getEquipmentSlot())) { previewTwoHanded = true; if (visibleEquipment.getEquipmentSlot() == ItemSlot.SUB_HAND.getSlotIdMask()) continue; // don't add players shield or sub hand weapon if previewing TwoHanded } if ((visibleEquipment.getEquipmentSlot() & previewItemsSlotMask) == 0) // add rest of players equipment previewItems.add(visibleEquipment); } } private static void schedulePreviewReset(Player player, int duration, boolean previewRobot) { PREVIEW_RESETS.compute(player.getObjectId(), (k, resetTask) -> { if (resetTask != null) { // cancel previous scheduled preview reset thread if (!previewRobot && player.isInRobotMode()) // restore robot appearance in case it was previewed just a few seconds ago updateRobotAppearance(player, player.getRobotId()); resetTask.cancel(true); } resetTask = ThreadPoolManager.getInstance().schedule(() -> { PacketSendUtility.sendPacket(player, new SM_CUSTOM_SETTINGS(player)); PacketSendUtility.sendPacket(player, new SM_UPDATE_PLAYER_APPEARANCE(player.getObjectId(), player.getEquipment().getEquippedForAppearance())); if (previewRobot && player.isInRobotMode()) updateRobotAppearance(player, player.getRobotId()); PacketSendUtility.sendMessage(player, "Preview time ended."); PREVIEW_RESETS.remove(player.getObjectId()); }, duration * 1000); return resetTask; }); } private static void updateRobotAppearance(Player player, int robotId) { PacketSendUtility.sendPacket(player, new SM_RIDE_ROBOT(player, 0)); PacketSendUtility.sendPacket(player, new SM_RIDE_ROBOT(player, robotId)); } }