Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/.idea
/target
/example-plugin/example-plugin.iml
/example-plugin/target
/oumlib-core/oumlib-core.iml
/oumlib-core/target
169 changes: 87 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,51 @@
[![](https://img.shields.io/jitpack/v/github/sun-mc-dev/oumlib?color=yellow&style=for-the-badge)](https://jitpack.io/#sun-mc-dev/oumlib)
[![](https://img.shields.io/badge/Java-21+-orange?style=for-the-badge&logo=openjdk)](https://adoptium.net/)
[![](https://img.shields.io/badge/Folia-Compatible-gold?style=for-the-badge)](https://github.com/PaperMC/Folia)
[![CodeFactor](https://www.codefactor.io/repository/github/sun-mc-dev/oumlib/badge)](https://www.codefactor.io/repository/github/sun-mc-dev/oumlib)

OumLib is a lightweight, utility-centric library designed for Minecraft servers (Paper/Spigot) and proxy networks (Velocity). Built around Java 21 virtual threads, it provides modern, type-safe, and thread-aware abstractions to eliminate boilerplate code.
A utility library for Paper and Velocity plugins. Shade it into your jar, call `OumLib.init(this)`, and you get commands, menus, configs, scheduling, cooldowns, events, regions, holograms, recipes, database access, and more — all with a fluent API and zero external dependencies at runtime.

OumLib is designed to be shaded and relocated directly into your plugin JAR.
Built on Java 21 virtual threads. Works on Paper, Folia, and Velocity.

---

## Quick Navigation

| Module | Description | Documentation |
|:---------------------|:-------------------------------------------------------------|:-------------------------------------------|
| **Setup & Platform** | Shaded setup lifecycle and platform detection utilities | **[Setup Guide](docs/setup.md)** |
| **Commands** | Fluent Brigadier command builders with cooldown support | **[Commands](docs/commands.md)** |
| **Configuration** | Automatic-reloading record configurations with comments | **[Configuration](docs/configuration.md)** |
| **Menus & GUIs** | Easy chest-layouts, button binding, paginated interfaces | **[Inventories](docs/inventories.md)** |
| **Scheduler** | Virtual-thread loops, TaskGroups, and Folia adaptors | **[Scheduler](docs/scheduler.md)** |
| **Events** | Chainable context-aware event registers with filters | **[Events](docs/events.md)** |
| **Math Utilities** | FastMath shortcuts, Vector3D, Volume3D, MathEval expressions | **[Math](docs/math.md)** |
| **Visual Effects** | Particle pathways: bezier curves, lines, helices | **[Visual Effects](docs/effects.md)** |
| **Display Entities** | Fluent transforms and DisplayBuilder controls | **[Display Entities](docs/entities.md)** |
| **Text & PAPI** | Kyori MiniMessage presets, placeholder hooks | **[Text & Placeholders](docs/text.md)** |
| **Database** | Asynchronous database connectors for SQLite and MySQL | **[Database](docs/database.md)** |
| **Plugin Bridges** | Auto-hooks for Economy, Permissions, Nexo and CustomItems | **[Bridges](docs/bridges.md)** |
| **General Utils** | PDC wrappers, duration parses, location serializing | **[Utilities](docs/utilities.md)** |
| **Web Hookers** | Asynchronous HTTP requests and Discord webhook builders | **[Web & Discord](docs/web.md)** |
## Docs

| Module | What it does | Link |
|:---------------------|:---------------------------------------------------------------------|:------------------------------------------|
| **Setup** | Init lifecycle, shading, platform detection | [setup.md](docs/setup.md) |
| **Commands** | Brigadier command builder with typed args, cooldowns, subcommands | [commands.md](docs/commands.md) |
| **Configuration** | Record-based YAML configs with auto-reload | [configuration.md](docs/configuration.md) |
| **Menus & Items** | Chest GUIs, paginated menus, ItemBuilder, DataComponents | [inventories.md](docs/inventories.md) |
| **Recipes** | Shaped, shapeless, cooking, smithing, stonecutting DSL | [recipes.md](docs/recipes.md) |
| **Scheduler** | Sync/async/virtual tasks, Promise, TaskChain, Countdown, Folia-aware | [scheduler.md](docs/scheduler.md) |
| **Events** | Functional event listeners with filters, expiry, one-shot | [events.md](docs/events.md) |
| **Cooldowns** | CooldownManager, RateLimiter, persistent stores | [cooldowns.md](docs/cooldowns.md) |
| **Text** | MiniMessage helpers, placeholders, localization, text input | [text.md](docs/text.md) |
| **PDC** | Type-safe persistent data, DataKey, PdcModel records, PdcTree | [pdc.md](docs/pdc.md) |
| **Metadata** | In-memory volatile data with TTL auto-cleanup | [metadata.md](docs/metadata.md) |
| **Holograms** | Packet-based virtual displays with click handling | [holograms.md](docs/holograms.md) |
| **Display Entities** | DisplayBuilder for text/item/block displays | [entities.md](docs/entities.md) |
| **Regions** | Cuboid, cylinder, sphere, polygon regions with enter/leave tracking | [regions.md](docs/regions.md) |
| **Math** | Vector2D/3D, Volume3D, Noise, Easing, FastMath, MathEval | [math.md](docs/math.md) |
| **Effects** | Particle effects — lines, circles, helices, bezier curves | [effects.md](docs/effects.md) |
| **Database** | Async SQLite/MySQL via HikariCP | [database.md](docs/database.md) |
| **Bridges** | Vault, Nexo, ItemsAdder, MMOItems hooks | [bridges.md](docs/bridges.md) |
| **Utilities** | Duration parsing, location serialization, formatting | [utilities.md](docs/utilities.md) |

---

## Installation

Declare the JitPack repository and OumLib dependency in your `pom.xml`.
### Maven

### 1. Add Repository
```xml
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
```

### 2. Add Dependency
```xml
<dependency>
<groupId>com.github.sun-mc-dev.oumlib</groupId>
Expand All @@ -54,11 +58,23 @@ Declare the JitPack repository and OumLib dependency in your `pom.xml`.
</dependency>
```

### 3. Shading & Relocation
You must relocate OumLib inside your package space to prevent classpath conflicts with other plugins running different versions of OumLib on the same server.
### Gradle (Kotlin DSL)

Add this configured `maven-shade-plugin` to your `pom.xml`:
```kotlin
repositories {
maven("https://jitpack.io")
}

dependencies {
implementation("com.github.sun-mc-dev.oumlib:oumlib-core:VERSION")
}
```

### Shading

You **must** shade and relocate OumLib into your plugin jar. This prevents version conflicts when multiple plugins use different OumLib versions on the same server.

**Maven:**
```xml
<build>
<plugins>
Expand Down Expand Up @@ -99,102 +115,91 @@ Add this configured `maven-shade-plugin` to your `pom.xml`:
</build>
```

---
**Gradle (Shadow):**
```kotlin
plugins {
id("com.gradleup.shadow") version "9.0.0-beta12"
}

## Quick Start Example
tasks.shadowJar {
relocate("dev.oum.oumlib", "your.plugin.package.libs.oumlib")
}
```

Here is a real-world scenario showing how to load a player profile asynchronously from a SQLite database, register a command to open a GUI shop, and play custom leveling sound/particle effects upon purchase:
---

```java
import dev.oum.oumlib.OumLib;
import dev.oum.oumlib.command.Commands;
import dev.oum.oumlib.config.ConfigManager;
import dev.oum.oumlib.config.ConfigSection;
import dev.oum.oumlib.database.Database;
import dev.oum.oumlib.effect.Effects;
import dev.oum.oumlib.inventory.ChestMenu;
import dev.oum.oumlib.inventory.ItemBuilder;
import dev.oum.oumlib.scheduler.Scheduler;
import dev.oum.oumlib.text.Text;
import dev.oum.oumlib.util.Permission;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
## Quick Start

public record ShopConfig(String itemTitle, int itemPrice) implements ConfigSection {}
A small plugin that loads config, registers a command, opens a shop GUI, and handles purchases:

public final class ProfileShopPlugin extends JavaPlugin {
```java
public final class ShopPlugin extends JavaPlugin {
private ConfigManager<ShopConfig> config;
private Database db;

@Override
public void onEnable() {
OumLib.init(this);
config = ConfigManager.of(ShopConfig.class, "shop.yml",

config = ConfigManager.of(ShopConfig.class, "shop.yml",
() -> new ShopConfig("<gold>Super Star</gold>", 100)
).enableAutoReload();

db = Database.sqlite(new File(getDataFolder(), "profiles.db"));
db.executeUpdate("CREATE TABLE IF NOT EXISTS economy (uuid VARCHAR(36) PRIMARY KEY, balance INT)");

Commands.literal("shop")
.permission(Permission.builder("myplugin.shop").build())
.executes(context -> {
if (!context.isPlayer()) {
return;
}
Player player = context.playerOrThrow();
db = Database.sqlite(new File(getDataFolder(), "data.db"));
db.executeUpdate("CREATE TABLE IF NOT EXISTS economy (uuid TEXT PRIMARY KEY, balance INT)");

db.executeQuery("SELECT balance FROM economy WHERE uuid = ?", player.getUniqueId().toString())
CommandBuilder.create("shop")
.description("Opens the shop")
.executes(ctx -> {
Player player = ctx.playerOrThrow();
db.executeQuery("SELECT balance FROM economy WHERE uuid = ?",
player.getUniqueId().toString())
.thenAcceptSync(rows -> {
int balance = rows.isEmpty() ? 500 : (int) rows.getFirst().get("balance");
openShopMenu(player, balance);
openShop(player, balance);
});
}).register();
})
.register();
}

private void openShopMenu(Player player, int balance) {
private void openShop(Player player, int balance) {
ChestMenu.builder()
.title("<dark_gray>Server Shop | Balance: " + balance + "</dark_gray>")
.title("<dark_gray>Shop | " + balance + " coins</dark_gray>")
.rows(3)
.pattern(
"#########",
"# P #",
"#########"
)
.bind('#', ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE).name(" ").build())
.bind('P', ItemBuilder.of(Material.NETHER_STAR).name(config.get().itemTitle()).lore("<yellow>Price: " + config.get().itemPrice() + "</yellow>").build())
.bind('P', ItemBuilder.of(Material.NETHER_STAR)
.name(config.get().itemTitle())
.lore("<yellow>Price: " + config.get().itemPrice() + "</yellow>")
.build())
.onClick('P', click -> {
Player p = click.player();
if (balance < config.get().itemPrice()) {
Text.send(click.player(), "<red>Insufficient balance!</red>");
click.player().closeInventory();
Text.send(p, "<red>Not enough coins!");
p.closeInventory();
return;
}

int newBalance = balance - config.get().itemPrice();
db.executeUpdate("INSERT INTO economy (uuid, balance) VALUES (?, ?) ON CONFLICT(uuid) DO UPDATE SET balance = ?",
click.player().getUniqueId().toString(), newBalance, newBalance);

Text.send(click.player(), "<green>Purchased successfully!</green>");
click.player().closeInventory();

Effects.sound(Sound.ENTITY_PLAYER_LEVELUP).volume(1.0F).pitch(1.2F).play(click.player());
Effects.particle(Particle.HAPPY_VILLAGER).count(15).offset(0.5, 0.5, 0.5).spawn(click.player().getLocation());
int newBal = balance - config.get().itemPrice();
db.executeUpdate(
"INSERT INTO economy VALUES (?,?) ON CONFLICT(uuid) DO UPDATE SET balance=?",
p.getUniqueId().toString(), newBal, newBal);
Text.send(p, "<green>Purchased!");
p.closeInventory();
})
.build()
.open(player);
}

@Override
public void onDisable() {
if (db != null) {
db.close();
}
if (db != null) db.close();
OumLib.shutdown();
}
}

public record ShopConfig(String itemTitle, int itemPrice) implements ConfigSection {}
```
Loading
Loading