or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

audience-system.mdbooks-and-inventory.mdboss-bars.mdevents-and-interactivity.mdindex.mdnbt-data-components.mdresource-packs.mdsound-system.mdtext-components.mdtext-formatting.mdtitles-and-subtitles.mdtranslation-system.md

resource-packs.mddocs/

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