Tutorials API. CeCILL-B license.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fr.zcraft.zlib.tools.tutorials; | |
import fr.zcraft.zlib.core.ZLib; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.player.PlayerKickEvent; | |
import org.bukkit.event.player.PlayerQuitEvent; | |
import org.bukkit.plugin.Plugin; | |
import org.bukkit.scheduler.BukkitTask; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.UUID; | |
import java.util.concurrent.ConcurrentHashMap; | |
/** | |
* <h1>Tutorial API</h1> | |
* | |
* <p> | |
* This API is used to display tutorials to any players. | |
* </p> | |
* <p> | |
* A tutorial is a set of {@code /title}s displayed to the players while they are frozen | |
* somewhere in the sky (or not). Tutorials are divided in chapters containing contents; | |
* each chapter contains a title displayed as the main {@code /title}, a location where | |
* the player is frozen during this chapter, and a list of “pages” displayed successively | |
* under that title. | |
* </p> | |
* <p> | |
* To use this, extend this class and use the following methods to create your tutorial. | |
* <ul> | |
* <li> | |
* {@link #addChapter(TutorialChapter)}—to add a chapter to this tutorial. | |
* Order matter! | |
* </li> | |
* <li> | |
* {@link #setReadingTime(long)}—to change the time each chapter's part is displayed. | |
* </li> | |
* <li> | |
* {@link #setTutorialHour(Long)}—to change the day of time this tutorial will be | |
* played. | |
* </li> | |
* </ul> | |
* </p> | |
* <p> | |
* Then, launch the tutorial using {@link #start(UUID)}. | |
* </p> | |
* <p> | |
* The tutorial can be force-stopped using {@link #stop(UUID)}. You can setup a callback | |
* executed when the tutorial is stopped by overriding {@link #onTutorialEnds(Player, boolean)}. | |
* </p> | |
* | |
* @author Amaury Carrade | |
*/ | |
public abstract class Tutorial implements Listener | |
{ | |
private final Plugin p; | |
/** | |
* Map: player's UUID -> task executing the tutorial | |
*/ | |
private Map<UUID, BukkitTask> viewers = new ConcurrentHashMap<>(); | |
/** | |
* Chapter's contents | |
*/ | |
private List<TutorialChapter> content = new LinkedList<>(); | |
private long timeNeededToPlayThisTutorial = 0l; | |
private long readingTime = 50l; | |
private Long tutorialHour = null; | |
public Tutorial() | |
{ | |
p = ZLib.getPlugin(); | |
p.getServer().getPluginManager().registerEvents(this, p); | |
} | |
/* * *** PUBLIC TUTORIAL API *** * */ | |
/** | |
* Adds a chapter in the tutorial. | |
* | |
* @param chapter The chapter to add. | |
*/ | |
public void addChapter(TutorialChapter chapter) | |
{ | |
content.add(chapter); | |
timeNeededToPlayThisTutorial += readingTime * chapter.getContent().size(); | |
} | |
/** | |
* Sets the amount of ticks each content of the tutorial is displayed. | |
* | |
* By default, 50 ticks (2.5 seconds). | |
* | |
* @param readingTime The ticks. | |
*/ | |
public void setReadingTime(final long readingTime) | |
{ | |
this.readingTime = readingTime; | |
} | |
/** | |
* The time of the day this tutorial is displayed. | |
* | |
* You can set a value above 24000 ticks if you want to play with moon phases.<br/> | |
* If {@code null}, the time is not changed. This is the default behavior. | |
* | |
* @param tutorialHour The time of the day this tutorial will be played. | |
*/ | |
public void setTutorialHour(final Long tutorialHour) | |
{ | |
this.tutorialHour = tutorialHour; | |
} | |
/** | |
* Returns this tutorial's duration. | |
* | |
* @return The duration, in ticks. | |
*/ | |
public long getTimeNeededToPlayThisTutorial() | |
{ | |
return timeNeededToPlayThisTutorial; | |
} | |
/* * *** TUTORIAL EVENTS *** * */ | |
/** | |
* This method is called when the tutorial is finished for the given player. | |
* | |
* @param player The player who just finished the tutorial. May be {@code null}. | |
* @param interrupted {@code true} if the tutorial is stopped because it was interrupted | |
* using {@link #stopForAll(String)}, {@link #stop(UUID)} or if the | |
* user disconnected. | |
*/ | |
protected void onTutorialEnds(Player player, boolean interrupted) {} | |
/* * *** TUTORIAL LAUNCHERS AND HALTERS *** * */ | |
/** | |
* Starts the tutorial for the given player. | |
* | |
* @param id The UUID of the player. | |
*/ | |
public void start(UUID id) | |
{ | |
if (isWatchingTutorial(id)) | |
{ | |
p.getLogger().info(p.getServer().getPlayer(id).getName() + "(" + id + ") is trying to see the tutorial whilst watching it."); | |
return; | |
} | |
Player player = p.getServer().getPlayer(id); | |
// The player cannot move anymore (except with our teleportations) | |
player.setFlySpeed(0f); | |
player.setAllowFlight(true); | |
player.setFlying(true); | |
// All other players are hidden | |
for (Player other : p.getServer().getOnlinePlayers()) | |
{ | |
player.hidePlayer(other); | |
other.hidePlayer(player); | |
} | |
// The player's hour is updated, if needed | |
if(tutorialHour != null) | |
{ | |
player.setPlayerTime(tutorialHour, false); | |
} | |
// The tutorial is started | |
viewers.put( | |
id, p.getServer().getScheduler().runTaskTimer(p, new TutorialRunner(this, id), 20l, readingTime) | |
); | |
} | |
/** | |
* Stops the tutorial for the given player. | |
* | |
* @param id The UUID of the player. | |
*/ | |
public void stop(UUID id) | |
{ | |
stop(id, true); | |
} | |
/** | |
* Stops the tutorial for everyone. | |
* | |
* @param reason A reason displayed to the viewers. Nothing sent if null. | |
*/ | |
public void stopForAll(String reason) | |
{ | |
for (UUID viewerID : viewers.keySet()) | |
{ | |
stop(viewerID, true); | |
if(reason != null) | |
{ | |
p.getServer().getPlayer(viewerID).sendMessage(reason); | |
} | |
} | |
} | |
/* * *** INTERNAL API *** * */ | |
/** | |
* Stops the tutorial for the given player. | |
* | |
* @param id The UUID of the player. | |
* @param interrupted {@code true} if the tutorial is stopped because it was interrupted | |
* using {@link #stopForAll(String)}, {@link #stop(UUID)} or if the | |
* user disconnected. | |
*/ | |
void stop(UUID id, final Boolean interrupted) // package-private | |
{ | |
if (!isWatchingTutorial(id)) return; | |
Player player = p.getServer().getPlayer(id); | |
if (player != null) | |
{ | |
// The player can now move. | |
player.setFlySpeed(0.1f); | |
player.setFlying(false); | |
player.setAllowFlight(false); | |
// All other players are displayed | |
for (Player other : p.getServer().getOnlinePlayers()) | |
{ | |
player.showPlayer(other); | |
other.showPlayer(player); | |
} | |
player.resetPlayerTime(); | |
} | |
try | |
{ | |
viewers.get(id).cancel(); | |
} | |
catch (IllegalStateException ignored) {} | |
viewers.remove(id); | |
onTutorialEnds(player, interrupted); | |
} | |
/** | |
* @return A list of {@link TutorialChapter}s. | |
*/ | |
List<TutorialChapter> getContent() | |
{ | |
return content; | |
} | |
/** | |
* Returns the amount of ticks each content of the tutorial is displayed. | |
* | |
* @return the amount of ticks each content of the tutorial is displayed. | |
*/ | |
long getReadingTime() | |
{ | |
return readingTime; | |
} | |
/** | |
* Checks if the given player is currently watching this tutorial. | |
* | |
* @param id The player's UUID. | |
* | |
* @return {@code true} if the player is watching this tutorial. | |
*/ | |
boolean isWatchingTutorial(UUID id) | |
{ | |
return viewers.containsKey(id); | |
} | |
/* * *** INTERNAL EVENTS *** * */ | |
@EventHandler | |
public void onPlayerQuits(PlayerQuitEvent ev) | |
{ | |
onPlayerQuits(ev.getPlayer()); | |
} | |
@EventHandler | |
public void onPlayerQuits(PlayerKickEvent ev) | |
{ | |
onPlayerQuits(ev.getPlayer()); | |
} | |
public void onPlayerQuits(Player player) | |
{ | |
if(isWatchingTutorial(player.getUniqueId())) | |
{ | |
stop(player.getUniqueId(), true); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fr.zcraft.zlib.tools.tutorials; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Location; | |
import org.bukkit.entity.Player; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.UUID; | |
/** | |
* Represents a chapter of an in-game tutorial. | |
* <p/> | |
* A chapter contains a title, displayed the whole time above, and one or more | |
* “pages”, displayed as subtitle of the {@code /title} command, ordered. | |
* | |
* @author Amaury Carrade | |
*/ | |
public class TutorialChapter | |
{ | |
private Location location; | |
private String title; | |
private List<String> content = new LinkedList<>(); | |
private boolean displayInChat = true; | |
/** | |
* @param location The watching point of the chapter. | |
* @param title The title of this chapter. | |
* @param content The content of this chapter. | |
*/ | |
public TutorialChapter(Location location, String title, List<String> content) | |
{ | |
this.location = location; | |
this.title = title; | |
this.content = content; | |
} | |
/** | |
* @param location The watching point of the chapter. | |
* @param title The title of this chapter. | |
* @param content The content of this chapter. | |
*/ | |
public TutorialChapter(Location location, String title, List<String> content, boolean displayInChat) | |
{ | |
this(location, title, content); | |
this.displayInChat = displayInChat; | |
} | |
/** | |
* Teleports the given player to the watching point of this chapter. | |
* | |
* @param uuid The UUID of the player. | |
*/ | |
public void teleport(UUID uuid) | |
{ | |
teleport(Bukkit.getPlayer(uuid)); | |
} | |
/** | |
* Teleports the given player to the watching point of this chapter. | |
* | |
* @param player The player. | |
*/ | |
public void teleport(Player player) | |
{ | |
player.teleport(location); | |
} | |
/** | |
* Returns the title of this chapter. | |
* | |
* @return The title. | |
*/ | |
public String getTitle() | |
{ | |
return title; | |
} | |
/** | |
* Returns the content of this chapter (a list of strings). | |
* | |
* @return The content. | |
*/ | |
public List<String> getContent() | |
{ | |
return content; | |
} | |
/** | |
* Returns true if this has to be displayed in the chat. | |
* | |
* @return {@code true} if displayed in the chat. | |
*/ | |
public boolean isDisplayedInChat() | |
{ | |
return displayInChat; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fr.zcraft.zlib.tools.tutorials; | |
import fr.zcraft.zlib.core.ZLib; | |
import fr.zcraft.zlib.tools.Titles; | |
import org.bukkit.ChatColor; | |
import org.bukkit.Sound; | |
import org.bukkit.entity.Player; | |
import org.bukkit.plugin.Plugin; | |
import java.util.UUID; | |
public class TutorialRunner implements Runnable | |
{ | |
private Plugin p; | |
private final String tutorialInChatPrefix = ChatColor.GRAY + "│ " + ChatColor.RESET; | |
private Player player; | |
private Tutorial tutorial; | |
private int currentChapter = 0; | |
private int currentText = 0; | |
public TutorialRunner(Tutorial tutorial, UUID playerId) | |
{ | |
this.p = ZLib.getPlugin(); | |
this.player = p.getServer().getPlayer(playerId); | |
this.tutorial = tutorial; | |
} | |
@Override | |
public void run() | |
{ | |
if (!player.isOnline()) | |
{ | |
tutorial.stop(player.getUniqueId(), true); | |
return; | |
} | |
if (currentChapter == tutorial.getContent().size()) // The end. | |
{ | |
tutorial.stop(player.getUniqueId(), false); | |
return; | |
} | |
TutorialChapter chapter = tutorial.getContent().get(currentChapter); | |
// Delays of fade-in, fade-out and display | |
int fadeIn = (currentText == 0) ? 10 : 0; | |
int fadeOut = (currentText == chapter.getContent().size() - 1) ? 10 : 0; | |
int readingTime = (int) (fadeOut == 10 ? tutorial.getReadingTime() - 10 : tutorial.getReadingTime() + 5); | |
// New chapter, new location | |
if (currentText == 0) | |
{ | |
chapter.teleport(player); | |
player.playSound(player.getLocation(), Sound.LEVEL_UP, 1L, 2L); | |
} | |
// Title version | |
Titles.displayTitle( | |
player, | |
fadeIn, readingTime, fadeOut, | |
chapter.getTitle(), | |
chapter.getContent().get(currentText) | |
); | |
// Chat version | |
if (chapter.isDisplayedInChat()) | |
{ | |
if (currentText == 0) player.sendMessage(tutorialInChatPrefix + chapter.getTitle()); | |
player.sendMessage(tutorialInChatPrefix + chapter.getContent().get(currentText)); | |
} | |
// Next one? | |
currentText++; | |
if (currentText == chapter.getContent().size()) | |
{ | |
currentChapter++; | |
currentText = 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment