|
1 | | - |
2 | | - |
3 | | - |
4 | | - |
| 1 | +<div align="center"> |
5 | 2 |
|
6 | | -Originally [DeadSouls](https://github.com/Astra-Interactive/DeadSoulsKT) |
| 3 | +# 👻 SoulKeeper |
7 | 4 |
|
8 | | -> [!CAUTION] |
9 | | -> Java 21 and Paper 1.21.1 only supported! Use other versions on your own risk! |
| 5 | +### Your items survive death. So does your progress. |
10 | 6 |
|
11 | | -# SoulKeeper |
| 7 | +SoulKeeper captures your full inventory and experience into a **glowing soul** the instant you die — persisting through lava, fire, and even the void — and waits quietly at the death spot until you return to claim it. |
12 | 8 |
|
13 | | -Plugin to prevent XP and/or Items loss on your death. The items will not despawn no matter what - lava, fire, void! |
| 9 | + |
14 | 10 |
|
15 | | -All your inventory will be safely stored inside a soul until someone will take it(in case of soul is freed). |
| 11 | +</div> |
16 | 12 |
|
17 | | -## Screenshot |
| 13 | +--- |
18 | 14 |
|
19 | | - |
| 15 | +## ✨ Inspiration |
| 16 | + |
| 17 | +SoulKeeper is the spiritual successor of [DeadSoulsKT](https://github.com/Astra-Interactive/DeadSoulsKT) — rebuilt from scratch with a clean multiplatform architecture, async-first design, and a strong focus on reliability. Where DeadSoulsKT laid the foundation, SoulKeeper brings production-grade persistence, rich visual feedback, and cross-platform support to the same beloved mechanic. |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## 💀 How Souls Work |
| 22 | + |
| 23 | +### Creating a soul |
| 24 | + |
| 25 | +The moment you die, SoulKeeper silently intercepts the death event before anything hits the ground. Your **entire inventory** and a configurable portion of your **dropped XP** are captured and stored in the database as a soul — nothing is scattered across the floor. |
| 26 | + |
| 27 | +A burst of red particles and a resonant bell signal that your soul has been born. The soul appears as a shimmering particle cloud with a floating nameplate — **"Soul of \<Your Name\>"** — right where you fell. |
| 28 | + |
| 29 | +> Souls spawned in The End are clamped to a minimum Y so they can never sink into the void. |
| 30 | +
|
| 31 | +### Picking up a soul |
| 32 | + |
| 33 | +Just **walk close** — no right-click, no shift, no interaction. As soon as you enter the soul's range, it detects you and begins transferring: |
| 34 | + |
| 35 | +| Signal | Meaning | |
| 36 | +|-----------------------------------|-------------------------------------| |
| 37 | +| 🔵 Cyan dust particles | Soul holds XP | |
| 38 | +| ⚪ White dust particles | Soul holds items | |
| 39 | +| 🔔 Beacon ambient sound | Soul is nearby — follow the sound | |
| 40 | +| 🟣 Purple smoke + anvil sound | Inventory full — items still remain | |
| 41 | +| ✨ Bright flash + extinguish sound | Soul fully collected and gone | |
| 42 | + |
| 43 | +Items flow back inventory-slot by inventory-slot. If your inventory is full, **partial pickup** works: XP is absorbed, whatever fits goes in, and the soul persists with only what remains — no items are ever dropped or lost because of a full bag. |
| 44 | + |
| 45 | +### Soul lifecycle |
| 46 | + |
| 47 | +``` |
| 48 | +Death → [PRIVATE] ──── soul_free_after ────▶ [PUBLIC] ──── soul_fade_after ────▶ [DELETED] |
| 49 | + │ │ |
| 50 | + Only owner Any player |
| 51 | + can collect can collect |
| 52 | +``` |
| 53 | + |
| 54 | +| State | Who can collect | Default timing | |
| 55 | +|----------------|-----------------|--------------------------------------------| |
| 56 | +| 🔒 **Private** | Owner only | Immediately after death | |
| 57 | +| 🔓 **Public** | Any player | After `soul_free_after` (default: 2 days) | |
| 58 | +| 💨 **Gone** | — | After `soul_fade_after` (default: 14 days) | |
| 59 | + |
| 60 | +Background workers (`FreeSoulWorker`, `DeleteSoulWorker`) run on a configurable tick cadence and advance souls through this lifecycle automatically — no cron jobs, no restarts, no admin action needed. |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## 🗃️ Krates — Backup Storage |
| 65 | + |
| 66 | +Every death also writes a **Krate**: a standalone YAML snapshot of the soul, saved to disk **independently of the database**. |
| 67 | + |
| 68 | +``` |
| 69 | +plugins/SoulKeeper/ |
| 70 | + deaths/ |
| 71 | + <player-uuid>/ |
| 72 | + <epoch-second>_0.yml ← first krate |
| 73 | + <epoch-second>_1.yml ← second krate (if retry occurred) |
| 74 | + ... |
| 75 | +``` |
| 76 | + |
| 77 | +Krates are a last-resort safety net. Even if the SQLite database is lost, corrupted, or rolled back, an admin can restore any dead player's exact inventory from the krate file — **zero database interaction required**. |
| 78 | + |
| 79 | +``` |
| 80 | +/soulkrate <uuid> <epoch-second> <index> |
| 81 | +``` |
| 82 | + |
| 83 | +This reads the matching `<epoch>_<index>.yml` krate file and deposits its items directly into the requesting player's inventory. Use it when: |
| 84 | + |
| 85 | +- A database failure occurred during a busy session |
| 86 | +- A player was rolled back by an external tool |
| 87 | +- Items need to be restored from a specific past death |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## 🚀 Features |
| 92 | + |
| 93 | +### 🔄 Fully non-blocking |
| 94 | + |
| 95 | +Every heavy operation — soul creation, pickup detection, database reads/writes, background worker loops — runs on **Kotlin coroutines** dispatched to background threads via `KotlinDispatchers`. The main game thread is never touched for I/O. |
| 96 | + |
| 97 | +### 🛡️ Safe, explicit failure paths |
| 98 | + |
| 99 | +Database operations return `kotlin.Result<T>`. Failures are logged at the call site and skipped gracefully — they never crash the server, silently eat items, or leave souls in a broken state. Concurrent DB writes in `SoulsDaoImpl`, the pickup loop in `PickUpWorker`, and death event handling in `ForgeSoulEvents`/`BukkitSoulEvents` are each guarded by their own `Mutex`. |
| 100 | + |
| 101 | +### 📦 Krate safety net |
| 102 | + |
| 103 | +A file-based snapshot is written on every death independently of the database. Items cannot be lost in a catastrophic DB failure. |
| 104 | + |
| 105 | +### 🎨 Fully customizable effects |
| 106 | + |
| 107 | +Every particle (type, color, size, count) and every sound (ID, volume, pitch) is independently configurable per event type. Six sound cues, five particle effects — all tweakable without touching code. |
| 108 | + |
| 109 | +### ⏳ Configurable soul lifecycle |
20 | 110 |
|
21 | | -## How to create a soul |
| 111 | +`soul_free_after` and `soul_fade_after` are plain duration values. Set them to seconds, minutes, hours, or days. Background services poll reactively from a config `Flow` — changes reload live without a restart. |
22 | 112 |
|
23 | | -- **Die** (in minecraft) |
24 | | -- Find the place of death. Or use `/souls` command. There will be a soul with your items and XP. Stand near soul a few |
25 | | - sec. Whoosh! Your items now collected |
26 | | -- If you don't have enough space, the soul will make a sound and purple smoke will appear indicating lack of space. |
27 | | -- The soul will become public after a configured time. |
| 113 | +### 📈 Partial pickup |
28 | 114 |
|
29 | | -Features: |
| 115 | +If your inventory is full when you reach a soul, XP is collected first, then as many items as fit go in. The soul survives with the remainder. Walk back once you have space — nothing is ever force-dropped. |
30 | 116 |
|
31 | | -- [x] Fully async |
32 | | -- [x] Highly customizable particle effects |
33 | | -- [x] Highly customizable sound effects |
34 | | -- [x] Runtime reloadable database |
35 | | -- [x] Control retained XP amount |
| 117 | +### 🏷️ Virtual floating name tags |
36 | 118 |
|
37 | | -| Command | Description | Permission | |
38 | | -|:-----------------|:-----------------------------|:--------------------| |
39 | | -| ️`/skreload` | Reload plugin | soulkeeper.reload | |
40 | | -| ️`/souls <page>` | List souls | `-` | |
41 | | -| ️`-` | View all souls in `/souls` | soulkeeper.all | |
42 | | -| ️`-` | Free any soul in `/souls` | soulkeeper.free.all | |
43 | | -| ️`-` | Teleport to soul in `/souls` | soulkeeper.teleport | |
| 119 | +Floating armor-stand nameplates ("Soul of \<Player\>") are sent as **virtual entities via PacketEvents** on Bukkit — no real entities are spawned into the world, so performance is unaffected regardless of soul count. |
44 | 120 |
|
45 | | -## Support |
| 121 | +### 🔔 Proximity soul calling |
46 | 122 |
|
47 | | -If anything go wrong, please: |
| 123 | +A `SoulCallWorker` tracks every online player's position. When a player enters `soul_call_radius` blocks of a soul they can collect, ambient sounds and particles begin pulsing — guiding them back even in complete darkness or deep underground. |
48 | 124 |
|
49 | | -1. Define a clear steps to reproduce |
50 | | -2. Try to reproduce again in clear environment |
51 | | -3. Obtain latest console logs |
52 | | -4. [Join Discord](https://discord.com/invite/8fEzV2TDS2) and create a ticket in `support` |
| 125 | +### 🌍 End dimension safety |
53 | 126 |
|
54 | | -## Feature request |
| 127 | +Souls spawned in The End are clamped to `endLocationLimitY` so they never fall below the island floor and become unreachable. |
55 | 128 |
|
56 | | -Almost the same as support. |
| 129 | +### 📑 Paginated soul browser |
57 | 130 |
|
58 | | -- Join discord |
59 | | -- Create a ticket in `suggestions` |
60 | | -- We will have a little talk and after that I will create issue on github |
| 131 | +`/souls` lists all visible souls with owner name, coordinates, age (formatted as "X days ago"), and item/XP indicators. Admins see clickable **[FREE]** and **[TP]** buttons inline. 5 souls per page; prev/next navigation included. |
61 | 132 |
|
62 | | -## Incompatible plugin |
| 133 | +### 🗄️ Versioned database migrations |
63 | 134 |
|
64 | | -- Any plugin which is editing `PlayerDeathEvent` |
| 135 | +`DatabaseMigrator` carries all schema migration steps. Upgrading the plugin never requires manual SQL — migrations run automatically on startup. |
65 | 136 |
|
66 | | -# Preview |
| 137 | +--- |
67 | 138 |
|
68 | | -## Killing |
| 139 | +## ⚙️ Configuration |
| 140 | + |
| 141 | +All settings live in `config.yml` inside the plugin data folder and are hot-reloadable via `/skreload`. |
| 142 | + |
| 143 | +```yaml |
| 144 | +# Database file path (SQLite) |
| 145 | +database: ... |
| 146 | + |
| 147 | +# Soul becomes publicly collectible after this duration |
| 148 | +soul_free_after: 2d |
| 149 | + |
| 150 | +# Soul disappears permanently after this duration |
| 151 | +soul_fade_after: 14d |
| 152 | + |
| 153 | +# Radius (blocks) within which a soul emits sounds/particles to guide its owner |
| 154 | +soul_call_radius: 100 |
| 155 | + |
| 156 | +# Fraction of dropped XP stored in the soul (0.0 – 1.0) |
| 157 | +retained_xp: 1.0 |
| 158 | + |
| 159 | +# Minimum Y for souls in The End (prevents void loss) |
| 160 | +end_location_limit_y: 0.0 |
| 161 | + |
| 162 | +# Show floating "Soul of <player>" name tags above souls |
| 163 | +display_soul_titles: true |
| 164 | + |
| 165 | +# How PvP deaths are handled |
| 166 | +# NONE – PvP deaths create no soul |
| 167 | +# EXP_ONLY – soul holds XP only |
| 168 | +# ITEMS_ONLY – soul holds items only |
| 169 | +# EXP_AND_ITEMS – soul holds everything (same as normal death) |
| 170 | +pvp_behaviour: NONE |
| 171 | +``` |
| 172 | +
|
| 173 | +### 🎵 Sounds |
| 174 | +
|
| 175 | +Every audio cue is independently configurable with `id`, `volume`, and `pitch`: |
| 176 | + |
| 177 | +| Key | Default sound | Trigger | |
| 178 | +|---------------------|----------------------------------|------------------------------| |
| 179 | +| `collect_xp` | `entity.experience_orb.pickup` | XP absorbed from soul | |
| 180 | +| `collect_item` | `item.trident.return` | Items absorbed from soul | |
| 181 | +| `soul_disappear` | `entity.generic.extinguish_fire` | Soul fully collected | |
| 182 | +| `soul_dropped` | `block.bell.resonate` | Soul created on death | |
| 183 | +| `soul_calling` | `block.beacon.ambient` | Soul pulsing near owner | |
| 184 | +| `soul_content_left` | `block.anvil.place` | Inventory full, items remain | |
| 185 | + |
| 186 | +### 🌟 Particles |
| 187 | + |
| 188 | +Every particle effect is independently configurable with `key`, `count`, and optional `dust_options` (RGBA color + size): |
| 189 | + |
| 190 | +| Key | Default color | Trigger | |
| 191 | +|---------------------|------------------|----------------------------------| |
| 192 | +| `soul_items` | White `#FFFFFF` | Soul contains items (continuous) | |
| 193 | +| `soul_xp` | Cyan `#00FFFF` | Soul contains XP (continuous) | |
| 194 | +| `soul_created` | Red `#eb3437` | Burst on soul creation | |
| 195 | +| `soul_gone` | Yellow `#FFFF00` | Burst on full collection | |
| 196 | +| `soul_content_left` | Purple `#a103fc` | Inventory full warning | |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +## 🛠️ Commands |
| 201 | + |
| 202 | +| Command | Description | Permission | |
| 203 | +|-------------------------------------|------------------------------------|-----------------------| |
| 204 | +| `/skreload` | Hot-reload config and translations | `soulkeeper.reload` | |
| 205 | +| `/souls [page]` | List your souls (5 per page) | *(none)* | |
| 206 | +| `/souls [page]` | List **all** server souls | `soulkeeper.all` | |
| 207 | +| `/souls` → **\[FREE\]** | Force-free any soul immediately | `soulkeeper.free.all` | |
| 208 | +| `/souls` → **\[TP\]** | Teleport to a soul's location | `soulkeeper.teleport` | |
| 209 | +| `/soulkrate <uuid> <epoch> <index>` | Restore items from a krate file | `soulkeeper.load` | |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## 🌐 Multiplatform |
| 214 | + |
| 215 | +SoulKeeper ships a **separate fat-jar** for each supported platform. All core logic — soul lifecycle, pickup detection, database, background workers, commands — lives in shared platform-agnostic modules. Only event wiring and item serialization are platform-specific. |
| 216 | + |
| 217 | +| Platform | Jar | Minimum version | |
| 218 | +|--------------------|---------------------------|-----------------| |
| 219 | +| **Paper / Spigot** | `soulkeeper-bukkit.jar` | Paper 1.21.1 | |
| 220 | +| **Forge** | `soulkeeper-forge.jar` | Forge 1.21.1 | |
| 221 | +| **NeoForge** | `soulkeeper-neoforge.jar` | NeoForge 1.21.1 | |
| 222 | + |
| 223 | +Pick the jar for your platform and drop it in — the same soul data, the same commands, the same behaviour everywhere. |
| 224 | + |
| 225 | +--- |
| 226 | + |
| 227 | +## 🎬 Preview |
| 228 | + |
| 229 | +### On death — soul created |
69 | 230 |
|
70 | 231 | https://github.com/user-attachments/assets/ec5f1437-0dab-404f-a4a8-725ee1cd9129 |
71 | 232 |
|
72 | | -## Collecting |
| 233 | +### Returning — soul collected |
73 | 234 |
|
74 | 235 | https://github.com/user-attachments/assets/75f58f92-7d13-4c23-a7fb-b2d89476748b |
75 | 236 |
|
76 | | -More plugins from AstraInteractive [AstraInteractive](https://github.com/Astra-Interactive) |
| 237 | +--- |
| 238 | + |
| 239 | +## ⚠️ Known Incompatibilities |
| 240 | + |
| 241 | +- Plugins that **cancel or heavily modify `PlayerDeathEvent`** (Bukkit) or the equivalent drop events (Forge/NeoForge) may prevent soul creation. Items may not be captured correctly when those plugins are present. |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +## 🐛 Support |
| 246 | + |
| 247 | +1. Reproduce the issue in a clean environment (no unrelated plugins/mods). |
| 248 | +2. Collect the full server log from startup through the issue. |
| 249 | +3. [Join our Discord](https://discord.com/invite/8fEzV2TDS2) and open a ticket in `#support`. |
| 250 | + |
| 251 | +## 💡 Feature Requests |
| 252 | + |
| 253 | +Open a ticket in `#suggestions` on our [Discord](https://discord.com/invite/8fEzV2TDS2) and we'll discuss it before creating a GitHub issue. |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +<div align="center"> |
77 | 258 |
|
78 | | -<img src="https://bstats.org/signatures/bukkit/SoulKeeper.svg"/> |
| 259 | +More plugins from [AstraInteractive](https://github.com/Astra-Interactive) |
| 260 | +</div> |
0 commit comments