net.minestom.server.advancements.AdvancementTab Maven / Gradle / Ivy
Show all versions of minestom-snapshots Show documentation
package net.minestom.server.advancements;
import net.minestom.server.Viewable;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Represents a tab which can be shared between multiple players. Created using {@link AdvancementManager#createTab(String, AdvancementRoot)}.
*
* Each tab requires a root advancement and all succeeding advancements need to have a parent in the tab.
* You can create a new advancement using {@link #createAdvancement(String, Advancement, Advancement)}.
*
* Be sure to use {@link #addViewer(Player)} and {@link #removeViewer(Player)} to control which players can see the tab.
* (all viewers will see the same tab, with the same amount of validated advancements etc... so shared).
*/
public class AdvancementTab implements Viewable {
private static final Map> PLAYER_TAB_MAP = new HashMap<>();
private final Set viewers = new HashSet<>();
private final AdvancementRoot root;
// Advancement -> its parent
private final Map advancementMap = new HashMap<>();
// the packet used to clear the tab (used to remove it and to update an advancement)
// will never change (since the root identifier is always the same)
protected final AdvancementsPacket removePacket;
protected AdvancementTab(@NotNull String rootIdentifier, @NotNull AdvancementRoot root) {
this.root = root;
cacheAdvancement(rootIdentifier, root, null);
this.removePacket = new AdvancementsPacket(false, List.of(), List.of(rootIdentifier), List.of());
}
/**
* Gets all the tabs of a viewer.
*
* @param player the player to get the tabs from
* @return all the advancement tabs that the player sees, can be null
* if the player doesn't see anything
*/
@Nullable
public static Set getTabs(@NotNull Player player) {
return PLAYER_TAB_MAP.getOrDefault(player.getUuid(), null);
}
/**
* Gets the root advancement of this tab.
*
* @return the root advancement
*/
@NotNull
public AdvancementRoot getRoot() {
return root;
}
/**
* Creates and add an advancement into this tab.
*
* @param identifier the unique identifier
* @param advancement the advancement to add
* @param parent the parent of this advancement, it cannot be null
*/
public void createAdvancement(@NotNull String identifier, @NotNull Advancement advancement, @NotNull Advancement parent) {
Check.stateCondition(!advancementMap.containsKey(parent),
"You tried to set a parent which doesn't exist or isn't registered");
cacheAdvancement(identifier, advancement, parent);
if (!getViewers().isEmpty()) {
sendPacketToViewers(advancement.getUpdatePacket());
}
}
/**
* Builds the packet which build the whole advancement tab.
*
* @return the packet adding this advancement tab and all its advancements
*/
protected @NotNull AdvancementsPacket createPacket() {
List mappings = new ArrayList<>();
List progressMappings = new ArrayList<>();
for (Advancement advancement : advancementMap.keySet()) {
mappings.add(advancement.toMapping());
progressMappings.add(advancement.toProgressMapping());
}
return new AdvancementsPacket(false, mappings, List.of(), progressMappings);
}
/**
* Caches an advancement.
*
* @param identifier the identifier of the advancement
* @param advancement the advancement
* @param parent the parent of this advancement, only null for the root advancement
*/
private void cacheAdvancement(@NotNull String identifier, @NotNull Advancement advancement, @Nullable Advancement parent) {
Check.stateCondition(advancement.getTab() != null,
"You tried to add an advancement already linked to a tab");
advancement.setTab(this);
advancement.setIdentifier(identifier);
advancement.setParent(parent);
advancement.updateCriteria();
this.advancementMap.put(advancement, parent);
}
@Override
public synchronized boolean addViewer(@NotNull Player player) {
final boolean result = viewers.add(player);
if (!result) return false;
// Send the tab to the player
player.sendPacket(createPacket());
addPlayer(player);
return true;
}
@Override
public synchronized boolean removeViewer(@NotNull Player player) {
if (!isViewer(player)) {
return false;
}
// Remove the tab
if (!player.isRemoved()) player.sendPacket(removePacket);
removePlayer(player);
return viewers.remove(player);
}
@NotNull
@Override
public Set getViewers() {
return viewers;
}
/**
* Adds the tab to the player set.
*
* @param player the player
*/
private void addPlayer(@NotNull Player player) {
Set tabs = PLAYER_TAB_MAP.computeIfAbsent(player.getUuid(), p -> new CopyOnWriteArraySet<>());
tabs.add(this);
}
/**
* Removes the tab from the player set.
*
* @param player the player
*/
private void removePlayer(@NotNull Player player) {
final UUID uuid = player.getUuid();
if (!PLAYER_TAB_MAP.containsKey(uuid)) {
return;
}
Set tabs = PLAYER_TAB_MAP.get(uuid);
tabs.remove(this);
if (tabs.isEmpty()) {
PLAYER_TAB_MAP.remove(uuid);
}
}
}