0
# Resource Packs
1
2
Resource pack management system for sending, tracking, and handling resource pack requests with callback support. Adventure provides comprehensive resource pack management for custom content delivery.
3
4
## Capabilities
5
6
### Resource Pack Request
7
8
Interface for creating and managing resource pack requests sent to audiences.
9
10
```java { .api }
11
/**
12
* Request to send resource pack to audience
13
*/
14
interface ResourcePackRequest extends Examinable {
15
/**
16
* Gets the resource pack information
17
* @return the resource pack info
18
*/
19
ResourcePackInfo packs();
20
21
/**
22
* Checks if this request replaces existing packs
23
* @return true if should replace existing packs
24
*/
25
boolean replace();
26
27
/**
28
* Gets the optional prompt message
29
* @return the prompt or null
30
*/
31
@Nullable Component prompt();
32
33
/**
34
* Gets the callback for pack events
35
* @return the callback or null
36
*/
37
@Nullable ResourcePackCallback callback();
38
39
/**
40
* Creates a resource pack request
41
* @param pack the resource pack info
42
* @return new request
43
*/
44
static ResourcePackRequest resourcePackRequest(ResourcePackInfo pack);
45
46
/**
47
* Creates a builder for resource pack requests
48
* @return new builder
49
*/
50
static Builder resourcePackRequest();
51
52
interface Builder extends AbstractBuilder<ResourcePackRequest> {
53
Builder packs(ResourcePackInfo packs);
54
Builder replace(boolean replace);
55
Builder prompt(@Nullable ComponentLike prompt);
56
Builder callback(@Nullable ResourcePackCallback callback);
57
}
58
}
59
```
60
61
### Resource Pack Info
62
63
Information about a resource pack including ID, URL, and hash for verification.
64
65
```java { .api }
66
/**
67
* Information about a resource pack
68
*/
69
interface ResourcePackInfo extends Examinable {
70
/**
71
* Gets the unique resource pack ID
72
* @return the pack ID
73
*/
74
String id();
75
76
/**
77
* Gets the download URL
78
* @return the pack URL
79
*/
80
String uri();
81
82
/**
83
* Gets the SHA-1 hash for verification
84
* @return the hash string
85
*/
86
String hash();
87
88
/**
89
* Creates resource pack info
90
* @param id the pack ID
91
* @param uri the download URL
92
* @param hash the SHA-1 hash
93
* @return new resource pack info
94
*/
95
static ResourcePackInfo resourcePackInfo(String id, String uri, String hash);
96
97
/**
98
* Creates a builder
99
* @return new builder
100
*/
101
static Builder builder();
102
103
interface Builder extends AbstractBuilder<ResourcePackInfo> {
104
Builder id(String id);
105
Builder uri(String uri);
106
Builder hash(String hash);
107
}
108
}
109
```
110
111
### Resource Pack Status
112
113
Enumeration of possible resource pack status values reported by clients.
114
115
```java { .api }
116
/**
117
* Status of resource pack download/application
118
*/
119
enum ResourcePackStatus {
120
/**
121
* Resource pack loaded successfully
122
*/
123
SUCCESSFULLY_LOADED("successfully_loaded"),
124
125
/**
126
* Player declined the resource pack
127
*/
128
DECLINED("declined"),
129
130
/**
131
* Failed to download the resource pack
132
*/
133
FAILED_DOWNLOAD("failed_download"),
134
135
/**
136
* Player accepted the resource pack prompt
137
*/
138
ACCEPTED("accepted"),
139
140
/**
141
* Resource pack downloaded successfully
142
*/
143
DOWNLOADED("downloaded"),
144
145
/**
146
* Invalid URL provided
147
*/
148
INVALID_URL("invalid_url"),
149
150
/**
151
* Failed to reload resource pack
152
*/
153
FAILED_TO_RELOAD("failed_to_reload"),
154
155
/**
156
* Resource pack was discarded
157
*/
158
DISCARDED("discarded");
159
}
160
```
161
162
### Resource Pack Callback
163
164
Callback interface for handling resource pack events and status updates.
165
166
```java { .api }
167
/**
168
* Callback interface for resource pack events
169
*/
170
interface ResourcePackCallback {
171
/**
172
* Called when a resource pack event occurs
173
* @param id the resource pack ID
174
* @param status the pack status
175
* @param audience the audience that triggered the event
176
*/
177
void packEventReceived(String id, ResourcePackStatus status, Audience audience);
178
179
/**
180
* Creates a simple callback
181
* @param callback the callback function
182
* @return new resource pack callback
183
*/
184
static ResourcePackCallback callback(ResourcePackEventConsumer callback);
185
186
/**
187
* Functional interface for resource pack events
188
*/
189
@FunctionalInterface
190
interface ResourcePackEventConsumer {
191
void accept(String id, ResourcePackStatus status, Audience audience);
192
}
193
}
194
```
195
196
**Usage Examples:**
197
198
```java
199
import net.kyori.adventure.resource.*;
200
import net.kyori.adventure.text.Component;
201
import net.kyori.adventure.text.format.NamedTextColor;
202
203
// Basic resource pack request
204
ResourcePackInfo pack = ResourcePackInfo.resourcePackInfo(
205
"my-pack-v1.0",
206
"https://example.com/packs/mypack.zip",
207
"abc123def456..." // SHA-1 hash
208
);
209
210
ResourcePackRequest request = ResourcePackRequest.resourcePackRequest(pack);
211
audience.sendResourcePacks(request);
212
213
// Resource pack with prompt and callback
214
ResourcePackRequest customRequest = ResourcePackRequest.resourcePackRequest()
215
.packs(pack)
216
.replace(true) // Replace existing packs
217
.prompt(Component.text("Download our custom textures?", NamedTextColor.YELLOW))
218
.callback((id, status, audience) -> {
219
switch (status) {
220
case SUCCESSFULLY_LOADED:
221
audience.sendMessage(Component.text("Resource pack loaded!", NamedTextColor.GREEN));
222
break;
223
case DECLINED:
224
audience.sendMessage(Component.text("Resource pack declined", NamedTextColor.RED));
225
break;
226
case FAILED_DOWNLOAD:
227
audience.sendMessage(Component.text("Failed to download resource pack", NamedTextColor.RED));
228
break;
229
}
230
})
231
.build();
232
233
audience.sendResourcePacks(customRequest);
234
235
// Remove resource packs
236
audience.removeResourcePacks(UUID.fromString("pack-uuid"));
237
audience.clearResourcePacks(); // Remove all packs
238
```
239
240
## Resource Pack Management Patterns
241
242
### Pack Version Management
243
244
```java
245
public class ResourcePackManager {
246
private final Map<String, ResourcePackInfo> packs = new HashMap<>();
247
248
public void registerPack(String name, String version, String url, String hash) {
249
String packId = name + "-" + version;
250
ResourcePackInfo pack = ResourcePackInfo.resourcePackInfo(packId, url, hash);
251
packs.put(name, pack);
252
}
253
254
public void sendLatestPack(Audience audience, String packName) {
255
ResourcePackInfo pack = packs.get(packName);
256
if (pack != null) {
257
ResourcePackRequest request = ResourcePackRequest.resourcePackRequest()
258
.packs(pack)
259
.replace(true)
260
.prompt(Component.text("Install " + packName + "?"))
261
.callback(createCallback(packName))
262
.build();
263
264
audience.sendResourcePacks(request);
265
}
266
}
267
268
private ResourcePackCallback createCallback(String packName) {
269
return (id, status, audience) -> {
270
logPackEvent(packName, id, status, audience);
271
handlePackStatus(packName, status, audience);
272
};
273
}
274
275
private void handlePackStatus(String packName, ResourcePackStatus status, Audience audience) {
276
switch (status) {
277
case SUCCESSFULLY_LOADED:
278
onPackLoaded(audience, packName);
279
break;
280
case DECLINED:
281
onPackDeclined(audience, packName);
282
break;
283
case FAILED_DOWNLOAD:
284
case INVALID_URL:
285
onPackFailed(audience, packName);
286
break;
287
}
288
}
289
}
290
```
291
292
### Conditional Pack Deployment
293
294
```java
295
public class ConditionalPacks {
296
public void sendPackBasedOnVersion(Audience audience, String clientVersion) {
297
ResourcePackInfo pack;
298
299
if (isVersion(clientVersion, "1.20")) {
300
pack = getPackForVersion("1.20");
301
} else if (isVersion(clientVersion, "1.19")) {
302
pack = getPackForVersion("1.19");
303
} else {
304
pack = getLegacyPack();
305
}
306
307
if (pack != null) {
308
sendPackWithVersionInfo(audience, pack, clientVersion);
309
}
310
}
311
312
public void sendOptionalPacks(Audience audience, Set<String> playerPreferences) {
313
List<ResourcePackInfo> packsToSend = new ArrayList<>();
314
315
if (playerPreferences.contains("hd-textures")) {
316
packsToSend.add(getHDTexturePack());
317
}
318
319
if (playerPreferences.contains("custom-sounds")) {
320
packsToSend.add(getCustomSoundPack());
321
}
322
323
if (playerPreferences.contains("ui-improvements")) {
324
packsToSend.add(getUIImprovementPack());
325
}
326
327
// Send packs sequentially or as a bundle
328
sendPackBundle(audience, packsToSend);
329
}
330
}
331
```
332
333
### Pack Status Tracking
334
335
```java
336
public class PackStatusTracker {
337
private final Map<String, Map<String, ResourcePackStatus>> playerPackStatus = new HashMap<>();
338
private final Set<ResourcePackStatusListener> listeners = new HashSet<>();
339
340
public ResourcePackCallback createTrackingCallback(String playerId) {
341
return (id, status, audience) -> {
342
updatePlayerPackStatus(playerId, id, status);
343
notifyListeners(playerId, id, status);
344
345
// Auto-retry on failure
346
if (status == ResourcePackStatus.FAILED_DOWNLOAD) {
347
scheduleRetry(audience, id);
348
}
349
};
350
}
351
352
private void updatePlayerPackStatus(String playerId, String packId, ResourcePackStatus status) {
353
playerPackStatus.computeIfAbsent(playerId, k -> new HashMap<>())
354
.put(packId, status);
355
}
356
357
public boolean hasPlayerLoadedPack(String playerId, String packId) {
358
return playerPackStatus.getOrDefault(playerId, Collections.emptyMap())
359
.get(packId) == ResourcePackStatus.SUCCESSFULLY_LOADED;
360
}
361
362
public Set<String> getLoadedPacks(String playerId) {
363
return playerPackStatus.getOrDefault(playerId, Collections.emptyMap())
364
.entrySet().stream()
365
.filter(entry -> entry.getValue() == ResourcePackStatus.SUCCESSFULLY_LOADED)
366
.map(Map.Entry::getKey)
367
.collect(Collectors.toSet());
368
}
369
370
private void scheduleRetry(Audience audience, String packId) {
371
// Implementation-specific retry logic
372
scheduleTask(() -> {
373
ResourcePackInfo pack = getPackById(packId);
374
if (pack != null) {
375
ResourcePackRequest retry = ResourcePackRequest.resourcePackRequest()
376
.packs(pack)
377
.prompt(Component.text("Retrying resource pack download..."))
378
.callback(createTrackingCallback(getPlayerId(audience)))
379
.build();
380
audience.sendResourcePacks(retry);
381
}
382
}, 5000L); // Retry after 5 seconds
383
}
384
}
385
```
386
387
### Pack Validation and Security
388
389
```java
390
public class PackValidator {
391
private static final int MAX_PACK_SIZE = 50 * 1024 * 1024; // 50MB
392
private static final Set<String> ALLOWED_DOMAINS = Set.of(
393
"example.com",
394
"cdn.example.com",
395
"packs.myserver.net"
396
);
397
398
public boolean validatePackInfo(ResourcePackInfo pack) {
399
// Validate URL
400
if (!isValidUrl(pack.uri())) {
401
return false;
402
}
403
404
// Validate hash format
405
if (!isValidSHA1Hash(pack.hash())) {
406
return false;
407
}
408
409
// Check domain whitelist
410
if (!isAllowedDomain(pack.uri())) {
411
return false;
412
}
413
414
return true;
415
}
416
417
private boolean isValidUrl(String url) {
418
try {
419
URL parsedUrl = new URL(url);
420
return "https".equals(parsedUrl.getProtocol()) ||
421
"http".equals(parsedUrl.getProtocol());
422
} catch (MalformedURLException e) {
423
return false;
424
}
425
}
426
427
private boolean isValidSHA1Hash(String hash) {
428
return hash != null &&
429
hash.length() == 40 &&
430
hash.matches("[a-fA-F0-9]+");
431
}
432
433
private boolean isAllowedDomain(String url) {
434
try {
435
String host = new URL(url).getHost();
436
return ALLOWED_DOMAINS.contains(host);
437
} catch (MalformedURLException e) {
438
return false;
439
}
440
}
441
442
public ResourcePackRequest createSecureRequest(ResourcePackInfo pack) {
443
if (!validatePackInfo(pack)) {
444
throw new IllegalArgumentException("Invalid resource pack info");
445
}
446
447
return ResourcePackRequest.resourcePackRequest()
448
.packs(pack)
449
.callback(createSecurityCallback())
450
.build();
451
}
452
453
private ResourcePackCallback createSecurityCallback() {
454
return (id, status, audience) -> {
455
if (status == ResourcePackStatus.INVALID_URL) {
456
logger.warn("Invalid URL detected for pack: {}", id);
457
} else if (status == ResourcePackStatus.FAILED_DOWNLOAD) {
458
logger.warn("Download failed for pack: {}", id);
459
}
460
};
461
}
462
}
463
```
464
465
## Best Practices
466
467
### Resource Pack Design
468
- Keep pack sizes reasonable (< 50MB recommended)
469
- Use appropriate compression for textures and sounds
470
- Test packs across different client versions
471
- Provide fallbacks for optional content
472
473
### URL and Security
474
- Use HTTPS URLs for security
475
- Implement domain whitelisting for pack sources
476
- Validate SHA-1 hashes to ensure integrity
477
- Monitor pack download success rates
478
479
### User Experience
480
- Provide clear prompts explaining what the pack contains
481
- Handle pack failures gracefully with helpful messages
482
- Allow players to opt-out of optional packs
483
- Provide pack management commands for players
484
485
### Performance
486
- Cache pack information to avoid repeated requests
487
- Implement retry logic for failed downloads
488
- Monitor pack loading times and success rates
489
- Use CDNs for pack distribution when possible