Skip to content

EffForceAttack Damage Entity By Amount #7878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: dev/feature
Choose a base branch
from
Open
122 changes: 86 additions & 36 deletions src/main/java/ch/njol/skript/effects/EffForceAttack.java
Original file line number Diff line number Diff line change
@@ -1,67 +1,117 @@
package ch.njol.skript.effects;

import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.RequiredPlugins;
import ch.njol.skript.doc.Since;
import ch.njol.skript.config.Node;
import ch.njol.skript.doc.*;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.SyntaxStringBuilder;
import ch.njol.util.Kleenean;
import org.bukkit.entity.Damageable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.log.runtime.SyntaxRuntimeErrorProducer;

@Name("Force Attack")
@Description("Makes a living entity attack an entity with a melee attack.")
@Description({
"Makes a living entity attack an entity with a melee attack.",
"Using 'attack' will make the attacker use the item in their main hand "
+ "and will apply extra data from the item, including enchantments and attributes.",
"Using 'damage' with a number of hearts will not account for the item in the main hand "
+ "and will always be the number provided."
})
@Example("""
spawn a wolf at location(0, 0, 0)
make last spawned wolf attack all players
""")
@Example("""
spawn a zombie at location(0, 0, 0)
make player damage last spawned zombie by 2
""")
@Examples({"spawn a wolf at player's location",
"make last spawned wolf attack player"})
@Since("2.5.1")
@Since("2.5.1, INSERT VERSION (multiple, amount)")
@RequiredPlugins("Minecraft 1.15.2+")
public class EffForceAttack extends Effect {
public class EffForceAttack extends Effect implements SyntaxRuntimeErrorProducer {

static {
Skript.registerEffect(EffForceAttack.class,
"make %livingentities% attack %entity%",
"force %livingentities% to attack %entity%");
"make %livingentities% attack %entities%",
"force %livingentities% to attack %entities%",
"make %livingentities% damage %entities% by %number% [heart[s]]",
"force %livingentities% to damage %entities% by %number% [heart[s]]");
}

private static final boolean ATTACK_IS_SUPPORTED = Skript.methodExists(LivingEntity.class, "attack", Entity.class);

@SuppressWarnings("null")
private Expression<LivingEntity> entities;
@SuppressWarnings("null")
private Expression<Entity> target;

private Expression<LivingEntity> attackers;
private Expression<Entity> victims;
private @Nullable Expression<Number> amount;
private Node node;

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
if (!ATTACK_IS_SUPPORTED) {
Skript.error("The force attack effect requires Minecraft 1.15.2 or newer");
return false;
}
entities = (Expression<LivingEntity>) exprs[0];
target = (Expression<Entity>) exprs[1];
attackers = (Expression<LivingEntity>) exprs[0];
victims = (Expression<Entity>) exprs[1];
if (matchedPattern >= 2)
amount = (Expression<Number>) exprs[2];
node = getParser().getNode();
return true;
}

@Override
protected void execute(Event e) {
Entity target = this.target.getSingle(e);
if (target != null) {
for (LivingEntity entity : entities.getArray(e)) {
entity.attack(target);
protected void execute(Event event) {
Double amount = null;
if (this.amount != null) {
Number number = this.amount.getSingle(event);
if (number == null)
return;
Double preAmount = number.doubleValue();
if (preAmount <= 0) {
error("Cannot damage an entity by 0 or less. Consider healing instead.");
return;
} else if (!Double.isFinite(preAmount)) {
return;
}
amount = preAmount * 2; // hearts
}

LivingEntity[] attackers = this.attackers.getArray(event);
Entity[] victims = this.victims.getArray(event);
if (amount == null) {
for (Entity victim : victims) {
for (LivingEntity attacker : attackers) {
attacker.attack(victim);
}
}
} else {
for (Entity victim : victims) {
if (!(victim instanceof Damageable damageable))
continue;
for (LivingEntity attacker : attackers) {
damageable.damage(amount, attacker);
}
}
}
}

@Override
public String toString(@Nullable Event e, boolean debug) {
return "make " + entities.toString(e, debug) + " attack " + target.toString(e, debug);
public Node getNode() {
return node;
}

@Override
public String toString(Event event, boolean debug) {
SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug);
builder.append("make", attackers);
if (amount == null) {
builder.append("attack", victims);
} else {
builder.append("damage", victims, "by", amount);
}
return builder.toString();
}

}
61 changes: 61 additions & 0 deletions src/test/skript/tests/syntaxes/effects/EffForceAttack.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

test "force attack":
spawn a zombie at test-location:
set {_attacker} to entity
spawn a pig at test-location:
set {_victim} to entity

set {_max} to the max health of {_victim}
make {_attacker} attack {_victim}
assert the health of {_victim} < {_max} with "Victim should have taken damage"
assert the last damage of {_victim} is 1.5 with "Damage taken should be 1.5"

clear entity within {_attacker}
clear entity within {_victim}

test "entity damage entity by amount":
spawn a zombie at test-location:
set {_attacker} to entity
spawn a pig at test-location:
set {_victim} to entity

set {_max} to the max health of {_victim}
make {_attacker} damage {_victim} by 5
assert the health of {_victim} < {_max} with "Victim should have taken damage"
assert the last damage of {_victim} is 5 with "Damage taken should be 5"

clear entity within {_attacker}
clear entity within {_victim}

test "entity damage entity by negative":
spawn a zombie at test-location:
set {_attacker} to entity
spawn a pig at test-location:
set {_victim} to entity

set {_max} to the max health of {_victim}
# TODO: Catch Runtime Error
make {_attacker} damage {_victim} by -2
assert the health of {_victim} is {_max} with "Victim's health should be max - negative value"
assert the last damage of {_victim} is 0 with "Victim should not have taken damage - negative value"

clear entity within {_attacker}
clear entity within {_victim}

test "entity damage entity by infinity/NaN":
spawn a zombie at test-location:
set {_attacker} to entity
spawn a pig at test-location:
set {_victim} to entity

set {_max} to the max health of {_victim}
make {_attacker} damage {_victim} by infinity value
assert the health of {_victim} is {_max} with "Victim's health should be max - infinity value"
assert the last damage of {_victim} is 0 with "Victim should not have taken damage - infinity value"

make {_attacker} damage {_victim} by NaN value
assert the health of {_victim} is {_max} with "Victim's health should be max - NaN value"
assert the last damage of {_victim} is 0 with "Victim should not have taken damage - NaN value"

clear entity within {_attacker}
clear entity within {_victim}