> For the complete documentation index, see [llms.txt](https://docs.ohalee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ohalee.com/products/modern-bedwars/for-developers.md).

# For Developers

The `api` module exposes stable, public interfaces for integrating with Modern BedWars — matches, parties, cosmetics and the Redis key catalogue. These are the same types shared across every module.

## Match

`Match<T extends MatchType>` describes a single game. Statuses flow `WAITING → STARTING → PLAYING → RESTARTING`; the first two are matchmaking phases.

```java
public interface Match<T extends MatchType> {
    @Nullable UUID hostUUID();
    int teamSize();
    int totalCount();
    boolean canCarry(int weight);
    T type();
    String name();
    void status(MatchStatus status);
    MatchStatus status();
    String map();
    int maxPlayers();
    String server();
    Date created();
    void closed(boolean closed);
    boolean closed();
}
```

```java
public enum MatchStatus {
    WAITING(true),
    STARTING(true),
    PLAYING(false),
    RESTARTING(false);
    // matchmaking() -> true for WAITING/STARTING
}
```

`MatchType` abstracts the game mode (Solo, Duo, Trio, Squad):

```java
public interface MatchType {
    String displayName();
    int maxTeams();
    int teamSize();
    int minPlayers();
}
```

## Party

Parties are immutable value objects — the `with*` methods return a new instance.

```java
public interface Party {
    UUID id();
    boolean isMember(UUID uuid);
    Set<UUID> members();
    UUID leader();
    boolean isLeader(UUID uuid);
    int size();
    Party withLeader(UUID newLeader);
    Party withMember(UUID member);
    Party withoutMember(UUID member);
}
```

### Integrating an external party plugin

`PartyProvider` is the **read-only** integration point for external party plugins (Party and Friends, Parties/AlessioDP, or your own). Set it via `bootstrap.getPartyManager().setPartyProvider(provider)`. When set, the built-in Redis party system is bypassed for reads and the Velocity `/party` command is disabled automatically.

```java
public interface PartyProvider {
    CompletionStage<Optional<Party>> getParty(UUID playerId);
    CompletionStage<Boolean> hasParty(UUID playerId);
    CompletionStage<Boolean> isLeader(UUID playerId);

    static PartyProvider noop(); // disable built-in commands without providing data
}
```

Use `PartyProvider.noop()` to disable the built-in party system entirely — e.g. on the Velocity side when a Bukkit-side party plugin handles everything.

## Cosmetics

A `Cosmetic` is a registered, unlockable effect in one of eight categories.

```java
public interface Cosmetic {
    int id();                              // matches the DB id
    String name();                         // internal name
    String displayName();
    List<String> description();
    CosmeticCategory category();
    CosmeticRarity rarity();
    int cost();                            // coin cost to unlock
    boolean isUnlockedByDefault();
    Optional<String> specialPermission();  // permission gate, if any
}
```

Categories (`CosmeticCategory`): `ARROW_TRAIL`, `FIREBALL_TRAIL`, `VICTORY_DANCE`, `FINAL_KILL_EFFECT`, `KILL_MESSAGE`, `BED_BREAK_EFFECT`, `BED_BREAK_MESSAGE`, `WOOD_SKIN`.

`CosmeticManager` registers cosmetics and manages per-player unlocks and active selections. Blocking operations return `CompletableFuture`s; there are cached (synchronous) variants for hot paths:

```java
public interface CosmeticManager {
    void registerCosmetic(Cosmetic cosmetic);
    Optional<Cosmetic> getCosmetic(String name);
    Optional<Cosmetic> getCosmetic(int id);
    List<Cosmetic> getCosmeticsByCategory(CosmeticCategory category);
    Collection<Cosmetic> getAllCosmetics();

    CompletableFuture<Boolean> hasUnlocked(UUID player, Cosmetic cosmetic);
    CompletableFuture<Boolean> unlockCosmetic(UUID player, Cosmetic cosmetic);
    CompletableFuture<Boolean> setActiveCosmetic(UUID player, Cosmetic cosmetic);
    void setAndForgetActiveCosmetic(UUID player, Cosmetic cosmetic);   // fire-and-forget
    CompletableFuture<Optional<Cosmetic>> getActiveCosmetic(UUID player, CosmeticCategory category);
    CompletableFuture<List<Cosmetic>> getUnlockedCosmetics(UUID player);

    // Cache-backed, synchronous — safe on the main thread once loadPlayerData completed
    Optional<Cosmetic> getCachedActiveCosmetic(UUID player, CosmeticCategory category);
    Optional<Cosmetic> getCachedEffectiveCosmetic(UUID player, CosmeticCategory category);
    List<Cosmetic> getCachedUnlockedCosmetics(UUID player);

    CompletableFuture<Void> loadPlayerData(UUID player);
    void unloadPlayerData(UUID player);
}
```

{% hint style="warning" %}
Never block on a `CompletableFuture` (`get()` / `join()`) on the main server thread. Use the cached variants once `loadPlayerData` has completed, or chain with `thenAcceptAsync`.
{% endhint %}

## Statistics

`StatsType` enumerates tracked stats and their PlaceholderAPI names:

| Stat             | Placeholder    |
| ---------------- | -------------- |
| `BEDS_BROKEN`    | `BED`          |
| `KILLS`          | `KILLS`        |
| `FINAL_KILLS`    | `FINALKILLS`   |
| `DEATHS`         | `DEATH`        |
| `FINAL_DEATHS`   | `FINALDEATHS`  |
| `WINS`           | `WINS`         |
| `LOSSES`         | `LOSSES`       |
| `WIN_STREAK`     | `WINSTREAK`    |
| `MAX_WIN_STREAK` | `MAXWINSTREAK` |
| `TOTAL_KILLS`    | `TOTALKILLS`   |

## Redis key catalogue

For diagnostics and low-level integration, `RedisKeys` centralises every key (`%s` are format arguments):

| Key                     | Pattern                   | Holds                             |
| ----------------------- | ------------------------- | --------------------------------- |
| `BEDWARS_MAPS`          | `bedwars:maps`            | Broadcast map list                |
| `BEDWARS_PLAYER_LOAD`   | `bedwars:player-load:%s`  | Per-server player load            |
| `DISGUISE_NAME`         | `bedwars:disguise`        | Disguise names                    |
| `DISGUISE_ACTIVE_NAMES` | `bedwars:active-disguise` | Active disguises                  |
| `PARTY`                 | `party:%s`                | Party hash (leader + fields)      |
| `PARTY_INVITE`          | `party:invite:%s:%s`      | Party invite data                 |
| `MATCH`                 | `bedwars:match:%s`        | Match hash                        |
| `PLAYER_IN_MATCH`       | `bedwars:player:%s`       | Player → match id                 |
| `PLAYER_REJOIN`         | `bedwars:rejoin:%s`       | Rejoin hash (TTL)                 |
| `SERVER`                | `bedwars:server:%s`       | Game-server heartbeat (short TTL) |

All cross-server traffic is Redis pub/sub via the internal `redis-bridge` library — there is no BungeeCord plugin-messaging channel.
