Boxed/src/main/java/world/bentobox/boxed/listeners/AdvancementListener.java

346 lines
14 KiB
Java

package world.bentobox.boxed.listeners;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Server;
import org.bukkit.Sound;
import org.bukkit.Statistic;
import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerAdvancementDoneEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import world.bentobox.bentobox.api.events.island.IslandNewIslandEvent;
import world.bentobox.bentobox.api.events.team.TeamJoinedEvent;
import world.bentobox.bentobox.api.events.team.TeamLeaveEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.boxed.Boxed;
/**
* @author tastybento
*
*/
public class AdvancementListener implements Listener {
private final Boxed addon;
private final Advancement netherAdvancement;
private final Advancement netherFortressAdvancement;
private final Advancement endAdvancement;
private final Advancement netherRoot;
private final Advancement endRoot;
/**
* @param addon addon
*/
public AdvancementListener(Boxed addon) {
this.addon = addon;
this.netherAdvancement = getAdvancement("minecraft:story/enter_the_nether");
this.endAdvancement = getAdvancement("minecraft:story/enter_the_end");
this.netherFortressAdvancement = getAdvancement("minecraft:nether/find_fortress");
this.netherRoot = getAdvancement("minecraft:nether/root");
this.endRoot = getAdvancement("minecraft:end/root");
}
/**
* Get Advancement given the namespaced key for it
* @param key namespaced key name for Advancement
* @return Advancement or null if none found
*/
public static Advancement getAdvancement(String key) {
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(Bukkit.advancementIterator(), Spliterator.ORDERED), false)
.filter(a -> a.getKey().toString().equals(key))
.findFirst().orElse(null);
}
/**
* Awards a bigger box when an advancement is done. Removes advancements if they are not valid.
* @param e PlayerAdvancementDoneEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onAdvancement(PlayerAdvancementDoneEvent e) {
// Ignore if player is not in survival
if (!e.getPlayer().getGameMode().equals(GameMode.SURVIVAL)) {
return;
}
// Check if player is in the Boxed worlds
if (addon.inWorld(e.getPlayer().getWorld())) {
// Only allow members or higher to get advancements in a box
if (addon.getSettings().isDenyVisitorAdvancements() && !addon.getIslands().getIslandAt(e.getPlayer().getLocation()).map(i -> i.getMemberSet().contains(e.getPlayer().getUniqueId())).orElse(false)) {
// Remove advancement from player
e.getAdvancement().getCriteria().forEach(c ->
e.getPlayer().getAdvancementProgress(e.getAdvancement()).revokeCriteria(c));
User u = User.getInstance(e.getPlayer());
if (u != null && addon.getAdvManager().getScore(e.getAdvancement().getKey().getKey()) > 0) {
u.notify("boxed.adv-disallowed", TextVariables.NAME, e.getPlayer().getName(), TextVariables.DESCRIPTION, this.keyToString(u, e.getAdvancement().getKey()));
}
return;
}
// Add the advancement to the island
int score = addon.getAdvManager().addAdvancement(e.getPlayer(), e.getAdvancement());
// Tell other team players one tick after it occurs if it is something that has a score
if (score != 0) {
User user = User.getInstance(e.getPlayer());
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> tellTeam(user, e.getAdvancement().getKey(), score));
}
}
}
private void tellTeam(User user, NamespacedKey key, int score) {
Island island = addon.getIslands().getIsland(addon.getOverWorld(), user);
if (island == null) {
// Something went wrong here
return;
}
island.getMemberSet().stream().map(User::getInstance).filter(User::isOnline)
.forEach(u -> {
informPlayer(u, key, score);
// Sync
grantAdv(u, addon.getAdvManager().getIsland(island).getAdvancements());
});
// Broadcast
if (addon.getSettings().isBroadcastAdvancements()) {
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Server.BROADCAST_CHANNEL_USERS))
.map(User::getInstance)
.forEach(u -> u.sendMessage("boxed.user-completed", TextVariables.NAME, user.getName(), TextVariables.DESCRIPTION, this.keyToString(u, key)));
}
}
/**
* Synchronize the player's advancements to that of the box.
* Player's advancements should be cleared before calling this otherwise they will get add the box ones as well.
* @param user - user
*/
public void syncAdvancements(User user) {
Island box = addon.getIslands().getIsland(addon.getOverWorld(), user);
if (box != null) {
grantAdv(user, addon.getAdvManager().getIsland(box).getAdvancements());
int diff = addon.getAdvManager().checkIslandSize(box);
if (diff > 0) {
user.sendMessage("boxed.size-changed", TextVariables.NUMBER, String.valueOf(diff));
user.getPlayer().playSound(Objects.requireNonNull(user.getLocation()), Sound.ENTITY_PLAYER_LEVELUP, 1F, 2F);
} else if (diff < 0) {
user.sendMessage("boxed.size-decreased", TextVariables.NUMBER, String.valueOf(Math.abs(diff)));
user.getPlayer().playSound(Objects.requireNonNull(user.getLocation()), Sound.ENTITY_VILLAGER_DEATH, 1F, 2F);
}
}
}
private void informPlayer(User user, NamespacedKey key, int score) {
user.getPlayer().playSound(Objects.requireNonNull(user.getLocation()), Sound.ENTITY_PLAYER_LEVELUP, 1F, 2F);
user.sendMessage("boxed.completed", TextVariables.NAME, keyToString(user, key));
user.sendMessage("boxed.size-changed", TextVariables.NUMBER, String.valueOf(score));
}
private String keyToString(User user, NamespacedKey key) {
String adv = user.getTranslationOrNothing("boxed.advancements." + key.toString());
if (adv.isEmpty()) {
adv = Util.prettifyText(key.getKey().substring(key.getKey().lastIndexOf("/") + 1));
}
return adv;
}
/**
* Special case Advancement awarding
* Awards the nether and end advancements when they portal for the first time.
* @param e
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPortal(PlayerPortalEvent e) {
if (!addon.inWorld(e.getPlayer().getWorld()) || !e.getPlayer().getGameMode().equals(GameMode.SURVIVAL)) {
return;
}
if (e.getCause().equals(TeleportCause.NETHER_PORTAL)) {
giveAdv(e.getPlayer(), netherAdvancement);
giveAdv(e.getPlayer(), netherRoot);
} else if (e.getCause().equals(TeleportCause.END_PORTAL)) {
giveAdv(e.getPlayer(), endAdvancement);
giveAdv(e.getPlayer(), endRoot);
}
}
/**
* Special case Advancement awarding
* Looks for certain blocks, and if they are found then awards an advancement
* @param e - PlayerMoveEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) {
if (!addon.getSettings().isNetherGenerate() || !Util.sameWorld(e.getPlayer().getWorld(), addon.getNetherWorld())) {
return;
}
// Nether fortress advancement
if (e.getTo().getBlock().getRelative(BlockFace.DOWN).getType().equals(Material.NETHER_BRICKS)) {
giveAdv(e.getPlayer(), netherFortressAdvancement);
}
}
/**
* Give player an advancement
* @param player - player
* @param adv - Advancement
*/
public static void giveAdv(Player player, Advancement adv) {
if (adv != null && !player.getAdvancementProgress(adv).isDone()) {
adv.getCriteria().forEach(player.getAdvancementProgress(adv)::awardCriteria);
}
}
/**
* Sync advancements when player joins server if they are in the Boxed world
* @param e PlayerJoinEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent e) {
User user = User.getInstance(e.getPlayer());
if (addon.inWorld(e.getPlayer().getWorld())) {
// Set advancements to same as island
syncAdvancements(user);
}
}
/**
* Sync advancements when player enters the Boxed world
* @param e PlayerChangedWorldEvent
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerEnterWorld(PlayerChangedWorldEvent e) {
User user = User.getInstance(e.getPlayer());
if (Util.sameWorld(addon.getOverWorld(), e.getPlayer().getWorld())) {
// Set advancements to same as island
syncAdvancements(user);
}
}
/**
* Clear and sync advancements for a player when they join a team if the settings require it
* @param e TeamJoinedEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onTeamJoinTime(TeamJoinedEvent e) {
User user = User.getInstance(e.getPlayerUUID());
if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline()
&& addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) {
// Clear and set advancements
clearAndSetAdv(user, addon.getSettings().isOnJoinResetAdvancements(), addon.getSettings().getOnJoinGrantAdvancements());
// Set advancements to same as island
syncAdvancements(user);
}
}
/**
* Clear player's advancements when they leave a team if the setting requires it
* @param e TeamLeaveEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onTeamLeaveTime(TeamLeaveEvent e) {
User user = User.getInstance(e.getPlayerUUID());
if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline()
&& addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) {
// Clear and set advancements
clearAndSetAdv(user, addon.getSettings().isOnLeaveResetAdvancements(), addon.getSettings().getOnLeaveGrantAdvancements());
}
}
/**
* Clear player's advancements when they start an island for the first time.
* @param e IslandNewIslandEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onFirstTime(IslandNewIslandEvent e) {
User user = User.getInstance(e.getPlayerUUID());
if (user != null) {
clearAndSetAdv(user, addon.getSettings().isOnJoinResetAdvancements(), addon.getSettings().getOnJoinGrantAdvancements());
}
}
/**
* Clear and set advancements for user. Will not do anything if the user is offline
* @param user - user
* @param clear - whether to clear advacements for this user or not
* @param list - list of advacements (namespaced keys) to grant to user
*/
private void clearAndSetAdv(User user, boolean clear, List<String> list) {
if (!user.isOnline()) {
return;
}
if (clear) {
clearAdv(user);
}
grantAdv(user, list);
}
/**
* Grant advancement to user
* @param user - user
* @param list - list of advancements to grant
*/
private void grantAdv(User user, List<String> list) {
Iterator<Advancement> it = Bukkit.advancementIterator();
while (it.hasNext()) {
Advancement a = it.next();
AdvancementProgress progress = user.getPlayer().getAdvancementProgress(a);
if (list.contains(a.getKey().toString()) && !progress.isDone()) {
// Award
a.getCriteria().forEach(progress::awardCriteria);
}
}
}
private void clearAdv(User user) {
// Clear Statistics
Arrays.stream(Statistic.values()).forEach(s -> resetStats(user, s));
// Clear advancements
Iterator<Advancement> it = Bukkit.advancementIterator();
while (it.hasNext()) {
Advancement a = it.next();
AdvancementProgress p = user.getPlayer().getAdvancementProgress(a);
p.getAwardedCriteria().forEach(p::revokeCriteria);
}
}
private void resetStats(User user, Statistic s) {
switch(s.getType()) {
case BLOCK -> Arrays.stream(Material.values()).filter(Material::isBlock).forEach(m -> user.getPlayer().setStatistic(s, m, 0));
case ITEM -> Arrays.stream(Material.values()).filter(Material::isItem).forEach(m -> user.getPlayer().setStatistic(s, m, 0));
case ENTITY -> Arrays.stream(EntityType.values()).filter(EntityType::isAlive).forEach(m -> user.getPlayer().setStatistic(s, m, 0));
case UNTYPED -> user.getPlayer().setStatistic(s, 0);
}
}
}