/*
* 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.effects;
import java.util.Arrays;
import java.util.logging.Level;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Skript;
import ch.njol.skript.classes.Changer;
import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.classes.ClassInfo;
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.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.Variable;
import ch.njol.skript.log.CountingLogHandler;
import ch.njol.skript.log.ErrorQuality;
import ch.njol.skript.log.ParseLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Patterns;
import ch.njol.skript.util.Utils;
import ch.njol.util.Kleenean;
/**
* @author Peter Güttinger
*/
@Name("Change: Set/Add/Remove/Delete/Reset")
@Description("A very general effect that can change many <a href='../expressions'>expressions</a>. Many expressions can only be set and/or deleted, while some can have things added to or removed from them.")
@Examples({"# set:",
"Set the player's display name to \"<red>%name of player%\"",
"set the block above the victim to lava",
"# add:",
"add 2 to the player's health # preferably use '<a href='#heal'>heal</a>' for this",
"add argument to {blacklist::*}",
"give a diamond pickaxe of efficiency 5 to the player",
"increase the data value of the clicked block by 1",
"# remove:",
"remove 2 pickaxes from the victim",
"subtract 2.5 from {points.%player%}",
"# remove all:",
"remove every iron tool from the player",
"remove all minecarts from {entitylist::*}",
"# delete:",
"delete the block below the player",
"clear drops",
"delete {variable}",
"# reset:",
"reset walk speed of player",
"reset chunk at the targeted block"})
@Since("1.0 (set, add, remove, delete), 2.0 (remove all)")
public class EffChange extends Effect {
private static Patterns<ChangeMode> patterns = new Patterns<ChangeMode>(new Object[][] {
{"(add|give) %objects% to %~objects%", ChangeMode.ADD},
{"increase %~objects% by %objects%", ChangeMode.ADD},
{"give %~objects% %objects%", ChangeMode.ADD},
{"set %~objects% to %objects%", ChangeMode.SET},
{"remove (all|every) %objects% from %~objects%", ChangeMode.REMOVE_ALL},
{"(remove|subtract) %objects% from %~objects%", ChangeMode.REMOVE},
{"reduce %~objects% by %objects%", ChangeMode.REMOVE},
{"(delete|clear) %~objects%", ChangeMode.DELETE},
{"reset %~objects%", ChangeMode.RESET}
});
static {
Skript.registerEffect(EffChange.class, patterns.getPatterns());
}
@SuppressWarnings("null")
private Expression<?> changed;
@Nullable
private Expression<?> changer = null;
@SuppressWarnings("null")
private ChangeMode mode;
private boolean single;
// private Changer<?, ?> c = null;
@SuppressWarnings({"unchecked", "null"})
@Override
public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) {
mode = patterns.getInfo(matchedPattern);
switch (mode) {
case ADD:
if (matchedPattern == 0) {
changer = exprs[0];
changed = exprs[1];
} else {
changer = exprs[1];
changed = exprs[0];
}
break;
case SET:
changer = exprs[1];
changed = exprs[0];
break;
case REMOVE_ALL:
changer = exprs[0];
changed = exprs[1];
break;
case REMOVE:
if (matchedPattern == 5) {
changer = exprs[0];
changed = exprs[1];
} else {
changer = exprs[1];
changed = exprs[0];
}
break;
case DELETE:
changed = exprs[0];
break;
case RESET:
changed = exprs[0];
}
final CountingLogHandler h = SkriptLogger.startLogHandler(new CountingLogHandler(Level.SEVERE));
final Class<?>[] rs;
final String what;
try {
rs = changed.acceptChange(mode);
final ClassInfo<?> c = Classes.getSuperClassInfo(changed.getReturnType());
final Changer<?> changer = c.getChanger();
what = changer == null || !Arrays.equals(changer.acceptChange(mode), rs) ? changed.toString(null, false) : c.getName().withIndefiniteArticle();
} finally {
h.stop();
}
if (rs == null) {
if (h.getCount() > 0)
return false;
switch (mode) {
case SET:
Skript.error(what + " can't be set to anything", ErrorQuality.SEMANTIC_ERROR);
break;
case DELETE:
if (changed.acceptChange(ChangeMode.RESET) != null)
Skript.error(what + " can't be deleted/cleared. It can however be reset which might result in the desired effect.", ErrorQuality.SEMANTIC_ERROR);
else
Skript.error(what + " can't be deleted/cleared", ErrorQuality.SEMANTIC_ERROR);
break;
case REMOVE_ALL:
if (changed.acceptChange(ChangeMode.REMOVE) != null) {
Skript.error(what + " can't have 'all of something' removed from it. Use 'remove' instead of 'remove all' to fix this.", ErrorQuality.SEMANTIC_ERROR);
break;
}
//$FALL-THROUGH$
case ADD:
case REMOVE:
Skript.error(what + " can't have anything " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " it", ErrorQuality.SEMANTIC_ERROR);
break;
case RESET:
if (changed.acceptChange(ChangeMode.DELETE) != null)
Skript.error(what + " can't be reset. It can however be deleted which might result in the desired effect.", ErrorQuality.SEMANTIC_ERROR);
else
Skript.error(what + " can't be reset", ErrorQuality.SEMANTIC_ERROR);
}
return false;
}
final Class<?>[] rs2 = new Class<?>[rs.length];
for (int i = 0; i < rs.length; i++)
rs2[i] = rs[i].isArray() ? rs[i].getComponentType() : rs[i];
final boolean allSingle = Arrays.equals(rs, rs2);
Expression<?> ch = changer;
if (ch != null) {
Expression<?> v = null;
final ParseLogHandler log = SkriptLogger.startParseLogHandler();
try {
for (final Class<?> r : rs) {
log.clear();
if ((r.isArray() ? r.getComponentType() : r).isAssignableFrom(ch.getReturnType())) {
v = ch.getConvertedExpression(Object.class);
break; // break even if v == null as it won't convert to Object apparently
}
}
if (v == null)
v = ch.getConvertedExpression((Class<Object>[]) rs2);
if (v == null) {
if (log.hasError()) {
log.printError();
return false;
}
log.clear();
log.printLog();
final Class<?>[] r = new Class[rs.length];
for (int i = 0; i < rs.length; i++)
r[i] = rs[i].isArray() ? rs[i].getComponentType() : rs[i];
if (rs.length == 1 && rs[0] == Object.class)
Skript.error("Can't understand this expression: " + changer, ErrorQuality.NOT_AN_EXPRESSION);
else if (mode == ChangeMode.SET)
Skript.error(what + " can't be set to " + changer + " because the latter is " + SkriptParser.notOfType(r), ErrorQuality.SEMANTIC_ERROR);
else
Skript.error(changer + " can't be " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " " + what + " because the former is " + SkriptParser.notOfType(r), ErrorQuality.SEMANTIC_ERROR);
return false;
}
log.printLog();
} finally {
log.stop();
}
Class<?> x = Utils.getSuperType(rs2);
single = allSingle;
for (int i = 0; i < rs.length; i++) {
if (rs2[i].isAssignableFrom(v.getReturnType())) {
single = !rs[i].isArray();
x = rs2[i];
break;
}
}
assert x != null;
changer = ch = v;
if (!ch.isSingle() && single) {
if (mode == ChangeMode.SET)
Skript.error(changed + " can only be set to one " + Classes.getSuperClassInfo(x).getName() + ", not more", ErrorQuality.SEMANTIC_ERROR);
else
Skript.error("only one " + Classes.getSuperClassInfo(x).getName() + " can be " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " " + changed + ", not more", ErrorQuality.SEMANTIC_ERROR);
return false;
}
if (changed instanceof Variable && !((Variable<?>) changed).isLocal() && (mode == ChangeMode.SET || ((Variable<?>) changed).isList() && mode == ChangeMode.ADD)) {
final ClassInfo<?> ci = Classes.getSuperClassInfo(ch.getReturnType());
if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null)
Skript.warning(ci.getName().withIndefiniteArticle() + " cannot be saved, i.e. the contents of the variable " + changed + " will be lost when the server stops.");
}
}
return true;
}
@Override
protected void execute(final Event e) {
final Expression<?> changer = this.changer;
final Object[] delta = changer == null ? null : changer.getArray(e);
if (delta != null && delta.length == 0)
return;
changed.change(e, delta, mode); // REMIND use a random element out of delta if changed only supports changing a single instance
// changed.change(e, new Changer2<Object>() {
// @Override
// public Object change(Object o) {
// return delta;
// }
// }, mode);
}
@Override
public String toString(final @Nullable Event e, final boolean debug) {
final Expression<?> changer = this.changer;
switch (mode) {
case ADD:
assert changer != null;
return "add " + changer.toString(e, debug) + " to " + changed.toString(e, debug);
case SET:
assert changer != null;
return "set " + changed.toString(e, debug) + " to " + changer.toString(e, debug);
case REMOVE:
assert changer != null;
return "remove " + changer.toString(e, debug) + " from " + changed.toString(e, debug);
case REMOVE_ALL:
assert changer != null;
return "remove all " + changer.toString(e, debug) + " from " + changed.toString(e, debug);
case DELETE:
return "delete/clear " + changed.toString(e, debug);
case RESET:
return "reset " + changed.toString(e, debug);
}
assert false;
return "";
}
}