DREAMFIRE Docs ← Back to site
Loading...
Searching...
No Matches
DreamBook.java
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Dreamfire Studio
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24package com.dreamfirestudios.dreamcore.DreamBook;
25
26import com.dreamfirestudios.dreamcore.DreamChat.DreamMessageFormatter;
27import com.dreamfirestudios.dreamcore.DreamChat.DreamMessageSettings;
28import com.dreamfirestudios.dreamcore.DreamCore;
29import com.dreamfirestudios.dreamcore.DreamJava.DreamClassID;
30import com.dreamfirestudios.dreamcore.DreamPersistentData.DreamPersistentItemStack;
31import lombok.Getter;
32import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
33import net.md_5.bungee.api.chat.TextComponent;
34import org.bukkit.Bukkit;
35import org.bukkit.Material;
36import org.bukkit.entity.Player;
37import org.bukkit.inventory.ItemStack;
38import org.bukkit.inventory.meta.BookMeta;
39import org.bukkit.persistence.PersistentDataType;
40
41import java.util.*;
42
51
52public class DreamBook extends DreamClassID {
53
55 public static final String DreamfireBookKey = "DreamfireBook";
56
57 @Getter private ItemStack book;
58 @Getter private BookMeta bookMeta;
59
60 private final List<String> pages = new ArrayList<>();
61 private final List<UUID> viewers = new ArrayList<>();
62
63 // --------------------------
64 // Viewer helpers
65 // --------------------------
66
72 public boolean isPlayerInBook(Player player){
73 if (player == null) throw new IllegalArgumentException("Player cannot be null.");
74 return viewers.contains(player.getUniqueId());
75 }
76
81 public void openBook(Player player){
82 if (player == null) throw new IllegalArgumentException("Player cannot be null.");
83
84 player.closeInventory(); // ensure a clean open
85 var openEvent = new DreamBookOpenEvent(this, player);
86 if (openEvent.isCancelled()) return;
87
88 player.openBook(book);
89
90 UUID id = player.getUniqueId();
91 if (!viewers.contains(id)) {
92 var added = new DreamBookViewerAddedEvent(this, player);
93 if (!added.isCancelled()) {
94 viewers.add(id);
95 }
96 }
97 }
98
104 public void openBook(Player player, int durationInSeconds) {
105 if (durationInSeconds < 0) throw new IllegalArgumentException("Duration cannot be negative.");
106 openBook(player);
107 Bukkit.getScheduler().runTaskLater(
109 () -> closeBook(player),
110 durationInSeconds * 20L
111 );
112 }
113
118 public void closeBook(Player player){
119 if (player == null) throw new IllegalArgumentException("Player cannot be null.");
120
121 UUID id = player.getUniqueId();
122 if (!viewers.contains(id)) return;
123
124 // Force-close sequence (reliable across client versions)
125 player.openInventory(Bukkit.createInventory(null, 9));
126 player.closeInventory();
127
128 new DreamBookCloseEvent(this, player);
129
130 var removed = new DreamBookViewerRemovedEvent(this, player);
131 if (!removed.isCancelled()) {
132 viewers.remove(id);
133 }
134 }
135
140 public void playerQuit(Player player){
141 if (player == null) throw new IllegalArgumentException("Player cannot be null.");
142 closeBook(player);
143 }
144
145 // --------------------------
146 // Pages helpers
147 // --------------------------
148
152 public List<String> getPagesView() {
153 return Collections.unmodifiableList(new ArrayList<>(pages));
154 }
155
160 public void setPages(List<String> newPages) {
161 if (newPages == null) throw new IllegalArgumentException("Pages cannot be null.");
162 pages.clear();
163 pages.addAll(newPages);
164 bookMeta.setPages(pages);
165 book.setItemMeta(bookMeta);
166 new DreamBookPagesUpdatedEvent(this, new ArrayList<>(pages));
167 }
168
172 public void addPages(String... additionalPages) {
173 if (additionalPages == null) throw new IllegalArgumentException("Pages cannot be null.");
174 for (String p : additionalPages) {
175 pages.add(PlainTextComponentSerializer.plainText()
176 .serialize(DreamMessageFormatter.format(p, DreamMessageSettings.all())));
177 }
178 bookMeta.setPages(pages);
179 book.setItemMeta(bookMeta);
180 new DreamBookPagesUpdatedEvent(this, new ArrayList<>(pages));
181 }
182
186 public void clearPages() {
187 pages.clear();
188 bookMeta.setPages(pages);
189 book.setItemMeta(bookMeta);
190 new DreamBookPagesUpdatedEvent(this, List.of());
191 }
192
193 // --------------------------
194 // Reserved for future animations
195 // --------------------------
196
200 public void displayNextFrame() {
201 // Intentionally left blank: opening books does not provide a reliable paged animation API.
202 // If approved, we can simulate page-flips by rebuilding items and re-opening per tick.
203 }
204
205 // --------------------------
206 // Builder
207 // --------------------------
208
213
214 public static class BookBuilder {
215 private final ItemStack book;
216 private final BookMeta bookMeta;
217 private final List<String> pages = new ArrayList<>();
218
224 public BookBuilder(String bookAuthor, String bookTitle) {
225 if (bookAuthor == null || bookTitle == null) {
226 throw new IllegalArgumentException("Author and title cannot be null.");
227 }
228 this.book = new ItemStack(Material.WRITTEN_BOOK);
229 this.bookMeta = (BookMeta) book.getItemMeta();
230 this.bookMeta.setAuthor(PlainTextComponentSerializer.plainText()
231 .serialize(DreamMessageFormatter.format(bookAuthor, DreamMessageSettings.all())));
232 this.bookMeta.setTitle(PlainTextComponentSerializer.plainText()
233 .serialize(DreamMessageFormatter.format(bookTitle, DreamMessageSettings.all())));
234 }
235
239 public BookBuilder generation(BookMeta.Generation generation) {
240 this.bookMeta.setGeneration(generation);
241 return this;
242 }
243
247 public BookBuilder bookPages(String... bookPages) {
248 if (bookPages == null) return this;
249 for (String page : bookPages) {
250 pages.add(PlainTextComponentSerializer.plainText()
251 .serialize(DreamMessageFormatter.format(page, DreamMessageSettings.all())));
252 }
253 return this;
254 }
255
259 @Deprecated
260 public BookBuilder bookPages(TextComponent... bookPages) {
261 // You can keep this for back-compat, but prefer the String-based API.
262 bookMeta.spigot().addPage(bookPages);
263 return this;
264 }
265
270 DreamBook dreamfireBook = new DreamBook();
271 if (!pages.isEmpty()) bookMeta.setPages(pages);
272 book.setItemMeta(bookMeta);
273
274 dreamfireBook.book = book;
275 dreamfireBook.bookMeta = bookMeta;
276 dreamfireBook.pages.addAll(pages);
277 DreamPersistentItemStack.Add(
279 book,
280 PersistentDataType.STRING,
282 dreamfireBook.getClassID().toString()
283 );
284 DreamCore.DreamBooks.put(dreamfireBook.getClassID(), dreamfireBook);
285 return dreamfireBook;
286 }
287 }
288}
Fired when a DreamBook is about to be opened for a player.
Fired when the pages of a DreamBook are updated (set/added/cleared).
Fired when a player is recorded as a viewer of a DreamBook.
Fired when a player is removed from the viewers of a DreamBook.
BookBuilder generation(BookMeta.Generation generation)
Sets the book generation (original/copy/etc.).
DreamBook createBook()
Builds a DreamfireBook and registers it for persistence.
BookBuilder(String bookAuthor, String bookTitle)
Creates a builder with the given author and title.
BookBuilder bookPages(TextComponent... bookPages)
Deprecated: legacy Spigot TextComponent pages.
BookBuilder bookPages(String... bookPages)
Adds pages (formatted) to this builder.
void openBook(Player player, int durationInSeconds)
Opens the book for a player for a fixed duration (seconds) before closing.
void closeBook(Player player)
Closes the book for a player and removes them from the viewer list.
boolean isPlayerInBook(Player player)
Checks whether a player is currently viewing this book.
void addPages(String... additionalPages)
Appends pages and updates the underlying BookMeta.
void setPages(List< String > newPages)
Replaces all pages and updates the underlying BookMeta.
void clearPages()
Removes all pages and updates the underlying BookMeta.
List< String > getPagesView()
Returns an immutable copy of current pages.
void playerQuit(Player player)
Should be called when a player leaves; ensures the book is closed for them.
void displayNextFrame()
Reserved hook for a future “book frame” animation (no-op).
static final String DreamfireBookKey
Persistent key for tagging book items in NBT.
void openBook(Player player)
Opens the book for a player and records them as a viewer.
static final LinkedHashMap< UUID, DreamBook > DreamBooks