/*
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/
package ch.njol.skript.expressions;
import org.bukkit.ChatColor;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Skript;
import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.classes.Changer.ChangerUtils;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.base.SimplePropertyExpression;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.util.Slot;
import ch.njol.util.Kleenean;
import ch.njol.util.coll.CollectionUtils;
/**
* @author Peter Güttinger
*/
@Name("Name / Display Name")
@Description({"Represents a player's minecraft account name, chat display name, or playerlist name, or the custom name of an item or <a href='../classes/#livingentity'>a living entity</a>.",
"The differences between the different names are:",
"<ul>",
"<li>name: Minecraft account name of a player (unmodifiable), or the custom name of an item or mob (modifiable).</li>",
"<li>display name: The name of a player as displayed in the chat and messages, e.g. when including %player% in a message. This name can be changed freely and can include colour codes, and is shared among all plugins (e.g. chat plugins will use a changed name).</li>",
"<li>tab list name: The name of a player used in the player lists that usually opens with the tab key. Please note that this is limited to 16 characters, including colour codes which are counted as 2 characters each, and that no two players can have the same tab list name at the same time.</li>",
"</ul>"})
@Examples({"on join:",
" player has permission \"name.red\"",
" set the player's display name to \"<red>[admin]<gold>%name of player%\"",
" set the player's tablist name to \"<green>%name of player%\"",
"set the name of the player's tool to \"Legendary Sword of Awesomeness\""})
@Since("1.4.6 (players' name & display name), <i>unknown</i> (player list name), 2.0 (item name)")
public class ExprName extends SimplePropertyExpression<Object, String> {
final static int ITEMSTACK = 1, ENTITY = 2, PLAYER = 4;
final static String[] types = {"slots/itemstacks", "livingentities", "players"};
private static enum NameType {
NAME("name", "name[s]", PLAYER | ITEMSTACK | ENTITY, ITEMSTACK | ENTITY) {
@Override
void set(final @Nullable Object o, final @Nullable String s) {
if (o == null)
return;
if (o instanceof LivingEntity) {
((LivingEntity) o).setCustomName(s);
((LivingEntity) o).setRemoveWhenFarAway(false);
} else if (o instanceof ItemStack) {
final ItemMeta m = ((ItemStack) o).getItemMeta();
if (m != null) {
m.setDisplayName(s);
((ItemStack) o).setItemMeta(m);
}
} else {
assert false;
}
}
@Override
@Nullable
String get(final @Nullable Object o) {
if (o == null)
return null;
if (o instanceof Player) {
return ((Player) o).getName();
} else if (o instanceof LivingEntity) {
return ((LivingEntity) o).getCustomName();
} else if (o instanceof ItemStack) {
if (!((ItemStack) o).hasItemMeta())
return null;
final ItemMeta m = ((ItemStack) o).getItemMeta();
return m == null || !m.hasDisplayName() ? null : m.getDisplayName();
} else {
assert false;
return null;
}
}
},
DISPLAY_NAME("display name", "(display|nick|chat)[ ]name[s]", PLAYER | ITEMSTACK | ENTITY, PLAYER | ITEMSTACK | ENTITY) {
@Override
void set(final @Nullable Object o, final @Nullable String s) {
if (o == null)
return;
if (o instanceof Player) {
((Player) o).setDisplayName(s == null ? ((Player) o).getName() : s + ChatColor.RESET);
} else if (o instanceof LivingEntity) {
((LivingEntity) o).setCustomName(s);
((LivingEntity) o).setCustomNameVisible(s != null);
((LivingEntity) o).setRemoveWhenFarAway(false);
} else if (o instanceof ItemStack) {
final ItemMeta m = ((ItemStack) o).getItemMeta();
if (m != null) {
m.setDisplayName(s);
((ItemStack) o).setItemMeta(m);
}
} else {
assert false;
}
}
@Override
@Nullable
String get(final @Nullable Object o) {
if (o == null)
return null;
if (o instanceof Player) {
return ((Player) o).getDisplayName();
} else if (o instanceof LivingEntity) {
return ((LivingEntity) o).getCustomName();
} else if (o instanceof ItemStack) {
if (!((ItemStack) o).hasItemMeta())
return null;
final ItemMeta m = ((ItemStack) o).getItemMeta();
return m == null || !m.hasDisplayName() ? null : m.getDisplayName();
} else {
assert false;
return null;
}
}
},
TABLIST_NAME("player list name", "(player|tab)[ ]list name[s]", PLAYER, PLAYER) {
@Override
void set(final @Nullable Object o, final @Nullable String s) {
if (o == null)
return;
if (o instanceof Player) {
try {
((Player) o).setPlayerListName(s == null ? "" : s.length() > 16 ? s.substring(0, 16) : s);
} catch (final IllegalArgumentException e) {}
} else {
assert false;
}
}
@Override
@Nullable
String get(final @Nullable Object o) {
if (o == null)
return null;
if (o instanceof Player) {
return ((Player) o).getPlayerListName();
} else {
assert false;
return null;
}
}
};
final String name;
final String pattern;
final int from;
final int acceptChange;
NameType(final String name, final String pattern, final int from, final int change) {
this.name = name;
this.pattern = "(" + ordinal() + "¦)" + pattern;
this.from = from;
acceptChange = change;
}
abstract void set(@Nullable Object o, @Nullable String s);
@Nullable
abstract String get(@Nullable Object o);
String getFrom() {
final StringBuilder b = new StringBuilder();
for (int i = 0; i < types.length; i++) {
if ((from & (1 << i)) == 0)
continue;
if ((1 << i) == ITEMSTACK && !Skript.isRunningMinecraft(1, 4, 5))
continue;
if ((1 << i) == ENTITY && !Skript.isRunningMinecraft(1, 5))
continue;
if (b.length() != 0)
b.append("/");
b.append(types[i]);
}
return "" + b;
}
}
static {
for (final NameType n : NameType.values())
register(ExprName.class, String.class, n.pattern, n.getFrom());
}
@SuppressWarnings("null")
private NameType type;
@SuppressWarnings("null")
@Override
public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) {
type = NameType.values()[parseResult.mark];
return super.init(exprs, matchedPattern, isDelayed, parseResult);
}
@Override
public Class<String> getReturnType() {
return String.class;
}
@Override
protected String getPropertyName() {
return type.name;
}
@Override
@Nullable
public String convert(final Object o) {
return type.get(o instanceof Slot ? ((Slot) o).getItem() : o);
}
private int changeType = 0;
// TODO find a better method for handling changes (in general)
// e.g. a Changer that takes an object and returns another which should then be saved if applicable (the Changer includes the ChangeMode)
@SuppressWarnings("unchecked")
@Override
@Nullable
public Class<?>[] acceptChange(final ChangeMode mode) {
if (mode == ChangeMode.DELETE && (type.acceptChange & ~PLAYER) != 0 || mode == ChangeMode.RESET)
return new Class[0];
if (mode != ChangeMode.SET)
return null;
if ((type.acceptChange & PLAYER) != 0 && Player.class.isAssignableFrom(getExpr().getReturnType())) {
changeType = PLAYER;
} else if ((type.acceptChange & ITEMSTACK) != 0 && (getExpr().isSingle() && ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, ItemStack.class, ItemType.class) || Slot.class.isAssignableFrom(getExpr().getReturnType()))) {
changeType = ITEMSTACK;
} else if ((type.acceptChange & ENTITY) != 0 && LivingEntity.class.isAssignableFrom(getExpr().getReturnType())) {
if (type == NameType.NAME && Player.class.isAssignableFrom(getExpr().getReturnType())) {
Skript.error("Cannot change the Minecraft name of a player. Change the 'display name of <player>' or 'tablist name of <player>' instead.");
return null;
}
changeType = ENTITY;
}
return changeType == 0 ? null : CollectionUtils.array(String.class);
}
@Override
public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException {
final String name = delta == null ? null : (String) delta[0];
if (changeType == ITEMSTACK) {
if (Slot.class.isAssignableFrom(getExpr().getReturnType())) {
for (final Slot s : (Slot[]) getExpr().getArray(e)) {
final ItemStack i = s.getItem();
type.set(i, name);
s.setItem(i);
}
} else {
final Object i = getExpr().getSingle(e);
if (!(i instanceof ItemStack) && !(i instanceof Slot))
return;
final ItemStack is = i instanceof Slot ? ((Slot) i).getItem() : (ItemStack) i;
type.set(is, name);
if (i instanceof Slot)
((Slot) i).setItem(is);
else if (ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, ItemStack.class))
getExpr().change(e, new Object[] {i}, ChangeMode.SET);
else
getExpr().change(e, new ItemType[] {new ItemType((ItemStack) i)}, ChangeMode.SET);
}
} else {
for (final Object o : getExpr().getArray(e)) {
if (o instanceof LivingEntity || o instanceof Player)
type.set(o, name);
}
}
}
}