Skip to content
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

Adds entity sound expression and test #7019

Merged
merged 11 commits into from
Oct 13, 2024
192 changes: 192 additions & 0 deletions src/main/java/ch/njol/skript/expressions/ExprEntitySound.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package ch.njol.skript.expressions;

import ch.njol.skript.Skript;
import ch.njol.skript.aliases.ItemType;
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.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.skript.util.Patterns;
import ch.njol.util.Kleenean;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

@Name("Entity Sound")
@Description("Gets the sound that a given entity will make in a specific scenario.")
@Examples({
"play sound (hurt sound of player) at player",
"set {_sounds::*} to death sounds of (all mobs in radius 10 of player)"
})
@Since("INSERT VERSION")
@RequiredPlugins("Spigot 1.19.2+")
public class ExprEntitySound extends SimpleExpression<String> {

public enum SoundType {
DAMAGE {
@Override
public @Nullable Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity.getHurtSound();
}
},
DEATH {
@Override
public @Nullable Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity.getDeathSound();
}
},
FALL {
@Override
public Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
if (height != -1)
return entity.getFallDamageSound(height);
else
return bigOrSpeedy ? entity.getFallDamageSoundBig() : entity.getFallDamageSoundSmall();
}
},
SWIM {
@Override
public Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity.getSwimSound();
}
},
SPLASH {
@Override
public Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return bigOrSpeedy ? entity.getSwimHighSpeedSplashSound() : entity.getSwimSplashSound();
}
},
EAT {
@Override
public Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity.getEatingSound(item);
}
},
DRINK {
@Override
public Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity.getDrinkingSound(item);
}
},
AMBIENT {
@Override
public @Nullable Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy) {
return entity instanceof Mob mob ? mob.getAmbientSound() : null;
}
};

public abstract @Nullable Sound getSound(LivingEntity entity, int height, ItemStack item, boolean bigOrSpeedy);
}

private static final Patterns<SoundType> patterns = new Patterns<>(new Object[][]{
{"[the] (damage|hurt) sound[s] of %livingentities%", SoundType.DAMAGE},
{"%livingentities%'[s] (damage|hurt) sound[s]", SoundType.DAMAGE},

{"[the] death sound[s] of %livingentities%", SoundType.DEATH},
{"%livingentities%'[s] death sound[s]", SoundType.DEATH},

{"[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%", SoundType.FALL},
{"%livingentities%'[s] [high:(tall|high)|low:(low|normal)] fall [damage] sound[s] [from [[a] height [of]] %-number%]", SoundType.FALL},

{"[the] swim[ming] sound[s] of %livingentities%", SoundType.SWIM},
{"%livingentities%'[s] swim[ming] sound[s]", SoundType.SWIM},

{"[the] [fast:(fast|speedy)] splash sound[s] of %livingentities%", SoundType.SPLASH},
{"%livingentities%'[s] [fast:(fast|speedy)] splash sound[s]", SoundType.SPLASH},

{"[the] eat[ing] sound[s] of %livingentities% [(with|using|[while] eating [a]) %-itemtype%]", SoundType.EAT},
{"%livingentities%'[s] eat[ing] sound[s]", SoundType.EAT},

{"[the] drink[ing] sound[s] of %livingentities% [(with|using|[while] drinking [a]) %-itemtype%]", SoundType.DRINK},
{"%livingentities%'[s] drink[ing] sound[s]", SoundType.DRINK},

{"[the] ambient sound[s] of %livingentities%", SoundType.AMBIENT},
{"%livingentities%'[s] ambient sound[s]", SoundType.AMBIENT}
});

static {
if (Skript.methodExists(LivingEntity.class, "getDeathSound"))
Skript.registerExpression(ExprEntitySound.class, String.class, ExpressionType.COMBINED, patterns.getPatterns());
}

private boolean bigOrSpeedy;
private SoundType soundType;
private Expression<Number> height;
private Expression<LivingEntity> entities;
private Expression<ItemType> item;

@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
soundType = patterns.getInfo(matchedPattern);
bigOrSpeedy = parseResult.hasTag("high") || parseResult.hasTag("fast");
if (soundType == SoundType.FALL)
height = (Expression<Number>) exprs[0];
if (soundType == SoundType.EAT || soundType == SoundType.DRINK)
item = (Expression<ItemType>) exprs[1];
entities = (Expression<LivingEntity>) ((soundType == SoundType.FALL) ? exprs[1] : exprs[0]);
return true;
}

@Override
protected String @Nullable [] get(Event event) {
int height = this.height == null ? -1 : this.height.getOptionalSingle(event).orElse(-1).intValue();

ItemStack defaultItem = new ItemStack(soundType == SoundType.EAT ? Material.COOKED_BEEF : Material.POTION);
ItemStack item = this.item == null ? defaultItem : this.item.getOptionalSingle(event).map(ItemType::getRandom).orElse(defaultItem);

return entities.stream(event)
.map(entity -> soundType.getSound(entity, height, item, bigOrSpeedy))
.filter(Objects::nonNull)
.distinct()
.map(Sound::name)
.toArray(String[]::new);
}

@Override
public boolean isSingle() {
return entities.isSingle();
}

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
String sound = "unknown";
switch (soundType) {
case DAMAGE, DEATH, SWIM, AMBIENT -> sound = soundType.name().toLowerCase();
case FALL -> {
if (this.height == null) {
sound = bigOrSpeedy ? "high fall damage" : "normal fall damage";
} else {
sound = "fall damage from a height of " + this.height.toString(event, debug);
}
}
case SPLASH -> sound = bigOrSpeedy ? "speedy splash" : "splash";
case EAT, DRINK -> {
String action = soundType == SoundType.EAT ? "eating" : "drinking";
if (this.item == null) {
sound = action;
} else {
sound = action + " " + this.item.toString(event, debug);
}
}
}
return sound + " sound of " + entities.toString(event, debug);
}

}
20 changes: 1 addition & 19 deletions src/main/java/ch/njol/skript/util/Patterns.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
/**
* 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 Peter Güttinger, SkriptLang team and contributors
*/
package ch.njol.skript.util;

import ch.njol.skript.lang.Expression;
Expand All @@ -24,7 +6,7 @@
import ch.njol.util.Kleenean;

/**
* A helper class useful when a expression/condition/effect/etc. needs to associate additional data with each pattern.
* A helper class useful when an expression/condition/effect/etc. needs to associate additional data with each pattern.
*
* @author Peter Güttinger
*/
Expand Down
17 changes: 17 additions & 0 deletions src/test/skript/tests/syntaxes/expressions/ExprEntitySound.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
test "entity sounds":
spawn a zombie at (spawn of world "world"):
cheeezburga marked this conversation as resolved.
Show resolved Hide resolved
assert damage sound of entity is "ENTITY_ZOMBIE_HURT" with "damage sound of zombie should return ENTITY_ZOMBIE_HURT"
assert death sound of entity is "ENTITY_ZOMBIE_DEATH" with "death sound of zombie should return ENTITY_ZOMBIE_DEATH"
assert swim sound of entity is "ENTITY_HOSTILE_SWIM" with "swim sound of zombie should return ENTITY_HOSTILE_SWIM"
assert normal fall damage sound of entity is "ENTITY_HOSTILE_SMALL_FALL" with "fall sound of zombie should return ENTITY_HOSTILE_SMALL_FALL"
assert high fall damage sound of entity is "ENTITY_HOSTILE_BIG_FALL" with "high fall sound of zombie should return ENTITY_HOSTILE_BIG_FALL"
assert fall damage sound from a height of 10 of entity is "ENTITY_HOSTILE_BIG_FALL" with "fall sound from height of 10 sound of zombie should return ENTITY_HOSTILE_BIG_FALL"
assert fall damage sound from a height of {_none} of entity is "ENTITY_HOSTILE_SMALL_FALL" with "fall sound from invalid height sound of zombie should return ENTITY_HOSTILE_SMALL_FALL"
assert splash sound of entity is "ENTITY_HOSTILE_SPLASH" with "splash sound of zombie should return ENTITY_HOSTILE_SPLASH"
assert speedy splash sound of entity is "ENTITY_GENERIC_SPLASH" with "speedy splash sound of zombie should return ENTITY_GENERIC_SPLASH"
assert eating sound of entity is "ENTITY_GENERIC_EAT" with "eating sound of zombie should return ENTITY_GENERIC_EAT"
assert eating sound of entity using golden apple is "ENTITY_GENERIC_EAT" with "eating sound of zombie using golden apple should return ENTITY_GENERIC_EAT"
assert drinking sound of entity is "ENTITY_GENERIC_DRINK" with "drinking sound of zombie should return ENTITY_GENERIC_DRINK"
assert drinking sound of entity using potion is "ENTITY_GENERIC_DRINK" with "drinking sound of zombie using potion should return ENTITY_GENERIC_DRINK"
assert ambient sound of entity is "ENTITY_ZOMBIE_AMBIENT" with "ambient sound of zombie should return ENTITY_ZOMBIE_AMBIENT"
delete entity