first
This commit is contained in:
@@ -0,0 +1,393 @@
|
|||||||
|
# SpaceShipProject
|
||||||
|
|
||||||
|
Plugin Paper / Spigot **1.21+** qui permet à chaque joueur d'avoir un (ou plusieurs) spaceship personnel.
|
||||||
|
Le vaisseau est chargé directement dans le ciel de la map actuelle ; il sert d'île volante privée que le
|
||||||
|
joueur peut décorer, fortifier puis ranger dans une "télécommande" en forme de **Recovery Compass**.
|
||||||
|
|
||||||
|
> Inspiré du fonctionnement des îles de Skyllia, mais sans monde dédié : tout reste sur la map du joueur,
|
||||||
|
> en plein ciel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sommaire
|
||||||
|
|
||||||
|
- [Fonctionnalités](#fonctionnalités)
|
||||||
|
- [Pré-requis](#pré-requis)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Compilation](#compilation)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Utilisation](#utilisation)
|
||||||
|
- [Télécommande (Recovery Compass)](#télécommande-recovery-compass)
|
||||||
|
- [GUI principal](#gui-principal)
|
||||||
|
- [Saut Spatial](#saut-spatial)
|
||||||
|
- [Commandes joueur (`/spaceship`, `/ss`, `/ship`)](#commandes-joueur-spaceship-ss-ship)
|
||||||
|
- [Commandes admin (`/spaceshipadmin`, `/ssa`)](#commandes-admin-spaceshipadmin-ssa)
|
||||||
|
- [Économie (Vault / EssentialsEconomy)](#économie-vault--essentialseconomy)
|
||||||
|
- [Système de niveaux et de tailles](#système-de-niveaux-et-de-tailles)
|
||||||
|
- [Stockage et base de données](#stockage-et-base-de-données)
|
||||||
|
- [Modèles de spaceship](#modèles-de-spaceship)
|
||||||
|
- [Permissions](#permissions)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
|
||||||
|
- Spaceships personnels matérialisés en plein ciel **sur la même map** que le joueur.
|
||||||
|
- Télécommande **Recovery Compass** par spaceship (clic gauche / droit / shift+droit).
|
||||||
|
- **Système de niveaux** : chaque level-up agrandit physiquement le vaisseau (x/z en alternance,
|
||||||
|
+1 y tous les 4 niveaux). Les nouvelles limites sont indiquées par des **Verdant Froglight**
|
||||||
|
aux 8 coins au premier chargement après l'upgrade.
|
||||||
|
- **Saut spatial** : déplacement instantané du spaceship le long d'un axe, avec un coût en money
|
||||||
|
proportionnel à la distance.
|
||||||
|
- **Sauvegarde complète** : chaque dépose stocke les blocs **et le contenu des coffres / barils /
|
||||||
|
fournaises / shulker boxes / etc.** dans la base de données.
|
||||||
|
- **Protection** : seul le propriétaire (ou un admin) peut casser/poser des blocs dans un
|
||||||
|
spaceship chargé. Les autres joueurs sont les bienvenus comme passagers.
|
||||||
|
- **Vault / EssentialsEconomy** : tous les coûts (invocation, upgrade, saut spatial) sont payés
|
||||||
|
via Vault. Aucun système monétaire interne — le solde du joueur est la source unique.
|
||||||
|
- **Auto-déchargement à la déconnexion** : tous les spaceships du joueur sont sauvegardés et
|
||||||
|
retirés du monde quand il se déconnecte.
|
||||||
|
- **Respawn sur le vaisseau** : si le joueur meurt sur un de ses spaceships chargés, il y respawn.
|
||||||
|
- **Modèles éditables** : un admin peut définir un plan par défaut (chargé en jeu, édité comme
|
||||||
|
des blocs normaux puis sauvegardé) qui sert de base à chaque nouveau spaceship.
|
||||||
|
- **GUI** complet pour gérer le ship : charger/décharger, téléport, set spawn, renommer, upgrade,
|
||||||
|
supprimer, saut spatial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pré-requis
|
||||||
|
|
||||||
|
- **Java 21+**
|
||||||
|
- **Paper / Purpur / Folia (compat partielle) 1.21.x**
|
||||||
|
- **Vault** (obligatoire pour l'économie)
|
||||||
|
- Un fournisseur d'économie compatible Vault (recommandé : **EssentialsEconomy** via EssentialsX)
|
||||||
|
|
||||||
|
> Sans Vault, le plugin charge mais désactive toutes les actions payantes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Copier `SpaceShipProject-1.0-SNAPSHOT.jar` dans le dossier `plugins/` du serveur.
|
||||||
|
2. S'assurer que `Vault` (+ un provider, ex. EssentialsX/EssentialsEconomy) est présent.
|
||||||
|
3. Démarrer le serveur. Le dossier `plugins/SpaceShipProject/` sera créé avec `config.yml`,
|
||||||
|
`models/` et `spaceships.db`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
Depuis la racine du dépôt `mcplugin/` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh SpaceShipProject
|
||||||
|
```
|
||||||
|
|
||||||
|
Le JAR final est dans `SpaceShipProject/target/SpaceShipProject-1.0-SNAPSHOT.jar`.
|
||||||
|
|
||||||
|
Pré-requis : Maven 3.6+, JDK 21. Le script gère automatiquement `JAVA_HOME` sur Arch Linux.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Fichier `plugins/SpaceShipProject/config.yml` :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
max-ships-per-player: 5
|
||||||
|
|
||||||
|
default-size: # taille initiale (level 1)
|
||||||
|
x: 4
|
||||||
|
y: 4
|
||||||
|
z: 6
|
||||||
|
|
||||||
|
max-size: # plafond atteignable
|
||||||
|
x: 16
|
||||||
|
y: 8
|
||||||
|
z: 16
|
||||||
|
|
||||||
|
costs:
|
||||||
|
summon: 100 # cout pour charger un spaceship
|
||||||
|
unload: 0 # cout pour decharger
|
||||||
|
upgrade-base: 1000 # cout d'amelioration = upgrade-base * niveau actuel
|
||||||
|
space-jump-per-block: 1
|
||||||
|
|
||||||
|
space-jump:
|
||||||
|
blocks-per-level: 100 # distance max = blocks-per-level * niveau
|
||||||
|
min-blocks: 1
|
||||||
|
max-blocks-hard-cap: 5000
|
||||||
|
|
||||||
|
summon-offset-y: 30 # hauteur du ship au-dessus du joueur a l'invocation
|
||||||
|
|
||||||
|
default-platform-material: SMOOTH_QUARTZ
|
||||||
|
default-edge-material: QUARTZ_PILLAR
|
||||||
|
default-glass-material: WHITE_STAINED_GLASS
|
||||||
|
|
||||||
|
default-ship-name: "Spaceship #%n%"
|
||||||
|
|
||||||
|
safety:
|
||||||
|
check-collision: true
|
||||||
|
min-y-above-player: 10
|
||||||
|
|
||||||
|
messages:
|
||||||
|
prefix: "&8[&bSpaceShip&8] &7"
|
||||||
|
# ... (voir le fichier complet)
|
||||||
|
```
|
||||||
|
|
||||||
|
Toutes les clés peuvent être modifiées à chaud via :
|
||||||
|
|
||||||
|
```
|
||||||
|
/ssa setconfig <chemin> <valeur>
|
||||||
|
/ssa getconfig <chemin>
|
||||||
|
/ssa reload
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### Télécommande (Recovery Compass)
|
||||||
|
|
||||||
|
Chaque spaceship a sa propre télécommande. L'item est un **Recovery Compass** marqué par PDC.
|
||||||
|
La lore montre l'ID, le nom, le niveau et la taille du ship.
|
||||||
|
|
||||||
|
| Action sur la télécommande | Effet |
|
||||||
|
|---|---|
|
||||||
|
| **Clic gauche** (au sol, ship non chargé) | Invoque le spaceship au-dessus du joueur (coût : `costs.summon`) |
|
||||||
|
| **Clic gauche** (au sol, ship chargé) | Décharge le ship et sauvegarde toutes les modifications |
|
||||||
|
| **Clic gauche** (sur le ship) | Refuse (utiliser clic droit pour redescendre d'abord) |
|
||||||
|
| **Clic droit** (au sol, ship non chargé) | Invoque puis téléporte directement sur le ship |
|
||||||
|
| **Clic droit** (au sol, ship chargé) | Téléporte sur le spawn du ship |
|
||||||
|
| **Clic droit** (sur le ship) | Redescend au sol, à la position initiale |
|
||||||
|
| **Shift + clic droit** | Ouvre le GUI de paramètres |
|
||||||
|
|
||||||
|
### GUI principal
|
||||||
|
|
||||||
|
Ouvert via **shift + clic droit** sur la télécommande. 36 slots :
|
||||||
|
|
||||||
|
```
|
||||||
|
. . . . I . . . . I = info (niveau, taille, statut, solde Vault)
|
||||||
|
. . . . . . . . .
|
||||||
|
. T E . S . . . . T = toggle charge/decharge
|
||||||
|
E = teleport up/down
|
||||||
|
S = set spawn (sur le ship)
|
||||||
|
J = Saut Spatial (sur le ship)
|
||||||
|
U = Ameliorer (level-up)
|
||||||
|
. . . . R . . . D R = renommer (par chat)
|
||||||
|
D = supprimer (shift-clic pour confirmer)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Saut Spatial
|
||||||
|
|
||||||
|
Disponible **uniquement quand le joueur est sur son spaceship**.
|
||||||
|
|
||||||
|
1. Ouvrir le GUI principal, cliquer sur **Saut Spatial**.
|
||||||
|
2. Choisir un axe : `X-`, `X+`, `Y-`, `Y+`, `Z-`, `Z+`.
|
||||||
|
3. Ajuster la distance avec les boutons (`-50 -10 -1 / +1 +10 +50`, shift = ×10).
|
||||||
|
4. Confirmer.
|
||||||
|
|
||||||
|
- Distance max = `space-jump.blocks-per-level × niveau` (par défaut 100 / niveau).
|
||||||
|
- Coût = `costs.space-jump-per-block × distance` (par défaut 1 money / bloc).
|
||||||
|
- Le ship est capturé, déchargé à l'ancienne position, rechargé à la nouvelle, et le joueur est
|
||||||
|
téléporté sur son spawn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes joueur (`/spaceship`, `/ss`, `/ship`)
|
||||||
|
|
||||||
|
| Commande | Description | Permission |
|
||||||
|
|---|---|---|
|
||||||
|
| `/ss help` | Aide | `spaceship.use` |
|
||||||
|
| `/ss create` | Crée un nouveau spaceship + donne sa télécommande | `spaceship.create` |
|
||||||
|
| `/ss give <joueur> <id>` | Redonne la télécommande perdue à un joueur (no-op si déjà présente, dédoublonne) | `spaceship.give` |
|
||||||
|
| `/ss remote <id>` | Redonne sa propre télécommande | `spaceship.give` |
|
||||||
|
| `/ss list [joueur]` | Liste compacte des ships | `spaceship.use` |
|
||||||
|
| `/ss info [joueur]` | Infos détaillées (niveau, taille actuelle/suivante, saut max, etc.) | `spaceship.use` / `spaceship.info.other` |
|
||||||
|
| `/ss delete <id>` | Supprime un ship et retire sa télécommande de l'inventaire | `spaceship.delete` |
|
||||||
|
| `/ss reload` | Recharge la config | `spaceship.reload` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes admin (`/spaceshipadmin`, `/ssa`)
|
||||||
|
|
||||||
|
| Commande | Description |
|
||||||
|
|---|---|
|
||||||
|
| `/ssa reload` | Recharge config + modèles |
|
||||||
|
| `/ssa list [joueur]` | Liste tous les ships, ou ceux d'un joueur, ou les ships actuellement chargés |
|
||||||
|
| `/ssa delete <id>` | Supprime n'importe quel ship |
|
||||||
|
| `/ssa addmoney <joueur> <montant>` | Dépose de la money via Vault sur le solde du joueur |
|
||||||
|
| `/ssa setlevel <joueur> <id> <level>` | Force le niveau d'un ship (la taille s'ajuste au prochain chargement) |
|
||||||
|
| `/ssa setname <joueur> <id> <nom...>` | Renomme un ship |
|
||||||
|
| `/ssa setconfig <chemin> <valeur>` | Modifie une clé du `config.yml` (auto-reload) |
|
||||||
|
| `/ssa getconfig <chemin>` | Lit une clé |
|
||||||
|
| `/ssa loadmodel [nom]` | Matérialise un modèle dans le ciel pour l'éditer (par défaut `default`) |
|
||||||
|
| `/ssa savemodel` | Capture la session en cours et l'écrit dans `models/<nom>.json` |
|
||||||
|
| `/ssa cancelmodel` | Annule la session et nettoie la zone |
|
||||||
|
|
||||||
|
Toutes ces commandes requièrent `spaceship.admin`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Économie (Vault / EssentialsEconomy)
|
||||||
|
|
||||||
|
Le plugin n'a **aucune** monnaie interne. Tout passe par **Vault** :
|
||||||
|
|
||||||
|
- Charger un spaceship → débite `costs.summon` au propriétaire.
|
||||||
|
- Améliorer un spaceship → débite `costs.upgrade-base × niveau actuel`.
|
||||||
|
- Saut spatial → débite `distance × costs.space-jump-per-block`.
|
||||||
|
|
||||||
|
La permission `spaceship.bypass.cost` permet de tester sans payer (par défaut OP).
|
||||||
|
|
||||||
|
Si Vault est absent / sans provider, le plugin log un warning et toutes les actions payantes
|
||||||
|
sont bloquées (`Vault indisponible`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Système de niveaux et de tailles
|
||||||
|
|
||||||
|
Au niveau 1, la taille est celle de `default-size` (par défaut **4 × 4 × 6**, x/y/z).
|
||||||
|
Chaque niveau gagné applique :
|
||||||
|
|
||||||
|
- **+1 sur X** (level-ups impairs : L1→L2, L3→L4, ...)
|
||||||
|
- **+1 sur Z** (level-ups pairs : L2→L3, L4→L5, ...)
|
||||||
|
- **+1 sur Y** tous les **4 level-ups** (L4→L5, L8→L9, ...)
|
||||||
|
|
||||||
|
…jusqu'au plafond `max-size` (par défaut **16 × 8 × 16**, atteint vers le niveau 27).
|
||||||
|
|
||||||
|
| Level | Taille (x,y,z) |
|
||||||
|
|------|----------------|
|
||||||
|
| 1 | 4 × 4 × 6 |
|
||||||
|
| 2 | 5 × 4 × 6 |
|
||||||
|
| 3 | 5 × 4 × 7 |
|
||||||
|
| 4 | 6 × 4 × 7 |
|
||||||
|
| 5 | 6 × 5 × 8 |
|
||||||
|
| ... | ... |
|
||||||
|
| 27+ | 16 × 8 × 16 |
|
||||||
|
|
||||||
|
Quand un ship est chargé pour la première fois après un upgrade, les nouvelles limites sont
|
||||||
|
matérialisées par des blocs **Verdant Froglight** aux 8 coins. Le joueur peut ensuite les
|
||||||
|
casser et décorer ses nouveaux murs librement — au prochain chargement, ces froglights ne
|
||||||
|
réapparaissent pas (sauf nouvel upgrade).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stockage et base de données
|
||||||
|
|
||||||
|
SQLite local : `plugins/SpaceShipProject/spaceships.db`.
|
||||||
|
|
||||||
|
Trois tables :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
players(
|
||||||
|
uuid TEXT PRIMARY KEY, name TEXT, settings TEXT,
|
||||||
|
created_at TIMESTAMP, updated_at TIMESTAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
spaceships(
|
||||||
|
id INTEGER PK,
|
||||||
|
owner_uuid TEXT,
|
||||||
|
name TEXT,
|
||||||
|
size_x, size_y, size_z INTEGER,
|
||||||
|
spawn_x, spawn_y, spawn_z INTEGER,
|
||||||
|
schematic TEXT, -- JSON (palette + RLE + containers)
|
||||||
|
settings TEXT, -- JSON libre
|
||||||
|
level INTEGER DEFAULT 1,
|
||||||
|
money INTEGER DEFAULT 0, -- obsolète (Vault), conservé pour migration
|
||||||
|
last_loaded_level INTEGER, -- pour le marquage Verdant Froglight
|
||||||
|
created_at TIMESTAMP, updated_at TIMESTAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
spaceships_loaded(
|
||||||
|
spaceship_id INTEGER PK,
|
||||||
|
world TEXT,
|
||||||
|
origin_x, origin_y, origin_z INTEGER,
|
||||||
|
loaded_at TIMESTAMP
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le champ `schematic` est un JSON :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"size": [sx, sy, sz],
|
||||||
|
"spawn": [ox, oy, oz],
|
||||||
|
"palette": ["minecraft:air", "minecraft:smooth_quartz", ...],
|
||||||
|
"blocks": [[paletteIdx, runLength], ...],
|
||||||
|
"containers": [
|
||||||
|
{"x":1,"y":2,"z":3,"items":[{"slot":0,"data":"base64..."}, ...]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Les `items` sont sérialisés via `ItemStack#serializeAsBytes()` : enchantements, NBT, custom
|
||||||
|
data, etc. sont entièrement préservés.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modèles de spaceship
|
||||||
|
|
||||||
|
Stockés dans `plugins/SpaceShipProject/models/<name>.json` (format identique au champ
|
||||||
|
`schematic`).
|
||||||
|
|
||||||
|
Workflow admin :
|
||||||
|
|
||||||
|
```text
|
||||||
|
/ssa loadmodel # place le modele actuel devant l'admin (sinon genere une plateforme)
|
||||||
|
# ... edite librement les blocs (admin = bypass de protection) ...
|
||||||
|
/ssa savemodel # capture la zone -> models/default.json, nettoie le ciel
|
||||||
|
```
|
||||||
|
|
||||||
|
Le prochain `/ss create` utilisera ce nouveau plan comme base. On peut créer plusieurs modèles
|
||||||
|
nommés (`/ssa loadmodel arena`, etc.).
|
||||||
|
|
||||||
|
Si `models/default.json` n'existe pas, le plugin génère une plateforme procédurale (sol +
|
||||||
|
piliers aux coins, vitres latérales).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spaceship.use: true # base : utiliser sa propre boussole
|
||||||
|
spaceship.create: true # /ss create
|
||||||
|
spaceship.give: true # /ss give (sa propre télécommande)
|
||||||
|
spaceship.info.other: op # /ss info <autre joueur>
|
||||||
|
spaceship.delete: true # supprimer un de ses ships
|
||||||
|
spaceship.reload: op
|
||||||
|
spaceship.admin: op # bypass de propriété + accès à /ssa
|
||||||
|
spaceship.bypass.cost: op # ne paie pas summon / upgrade / saut
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Que se passe-t-il si je me déconnecte alors que mon ship est chargé ?**
|
||||||
|
Le plugin capture l'état complet (y compris coffres) et décharge le ship. Vous êtes téléporté
|
||||||
|
au sol au préalable pour ne pas réapparaître en plein vol à la reconnexion.
|
||||||
|
|
||||||
|
**Et si je meurs sur mon ship ?**
|
||||||
|
Vous respawnez directement sur le spawn du ship si celui-ci est encore chargé. Sinon, respawn
|
||||||
|
normal.
|
||||||
|
|
||||||
|
**Le ship peut-il chevaucher des constructions du joueur ?**
|
||||||
|
Non : le plugin vérifie qu'aucun bloc solide n'occupe la zone avant l'invocation
|
||||||
|
(`safety.check-collision`). Si oui, l'invocation échoue avec un message d'erreur.
|
||||||
|
|
||||||
|
**Puis-je inviter d'autres joueurs sur mon ship ?**
|
||||||
|
Oui — ils peuvent y monter (téléport-relay si tu leur indiques la position). Cependant, ils ne
|
||||||
|
peuvent **pas modifier les blocs** : seuls le propriétaire et les admins peuvent casser/poser.
|
||||||
|
|
||||||
|
**Comment réinitialiser un ship à zéro ?**
|
||||||
|
Le supprimer (`/ss delete <id>`) puis en recréer un (`/ss create`). La télécommande de l'ancien
|
||||||
|
est retirée automatiquement.
|
||||||
|
|
||||||
|
**Que se passe-t-il quand le ship atteint la taille maximale ?**
|
||||||
|
Le bouton "Améliorer" devient inactif. Vous gardez votre ship à taille max.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Plugin développé pour un usage privé. Pas de licence formelle, à utiliser librement.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.spaceshipproject</groupId>
|
||||||
|
<artifactId>SpaceShipProject</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<name>SpaceShipProject</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.21.1-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial</groupId>
|
||||||
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
<version>3.44.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.MilkBowl</groupId>
|
||||||
|
<artifactId>VaultAPI</artifactId>
|
||||||
|
<version>1.7.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>21</source>
|
||||||
|
<target>21</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.sqlite</pattern>
|
||||||
|
<shadedPattern>com.spaceshipproject.libs.sqlite</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||||
|
import org.bukkit.event.player.PlayerBucketFillEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empeche tout joueur autre que le proprietaire (ou un admin) de modifier les blocs
|
||||||
|
* a l'interieur de la bounding box d'un spaceship charge.
|
||||||
|
*/
|
||||||
|
public class BlockProtectionListener implements Listener {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final SpaceshipManager manager;
|
||||||
|
private final ConfigManager cfg;
|
||||||
|
|
||||||
|
public BlockProtectionListener(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.manager = plugin.getSpaceshipManager();
|
||||||
|
this.cfg = plugin.getConfigManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onBreak(BlockBreakEvent event) {
|
||||||
|
if (denyIfNotOwner(event.getPlayer(), event.getBlock().getLocation())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onPlace(BlockPlaceEvent event) {
|
||||||
|
if (denyIfNotOwner(event.getPlayer(), event.getBlock().getLocation())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onBucketEmpty(PlayerBucketEmptyEvent event) {
|
||||||
|
if (denyIfNotOwner(event.getPlayer(), event.getBlockClicked().getRelative(event.getBlockFace()).getLocation())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onBucketFill(PlayerBucketFillEvent event) {
|
||||||
|
if (denyIfNotOwner(event.getPlayer(), event.getBlockClicked().getLocation())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onInteract(PlayerInteractEvent event) {
|
||||||
|
Block b = event.getClickedBlock();
|
||||||
|
if (b == null) return;
|
||||||
|
if (denyIfNotOwner(event.getPlayer(), b.getLocation())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean denyIfNotOwner(Player player, Location loc) {
|
||||||
|
if (loc.getWorld() == null) return false;
|
||||||
|
SpaceshipManager.LoadedShipOwnerInfo info = manager.findShipOwnerAt(
|
||||||
|
loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||||
|
if (info == null) return false;
|
||||||
|
if (info.ownerUuid().equals(player.getUniqueId())) return false;
|
||||||
|
if (player.hasPermission("spaceship.admin")) return false;
|
||||||
|
player.sendMessage(cfg.msgNotOwner());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acces type-safe a la config (config.yml).
|
||||||
|
*/
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
|
||||||
|
public ConfigManager(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileConfiguration cfg() {
|
||||||
|
return plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int maxShipsPerPlayer() { return cfg().getInt("max-ships-per-player", 5); }
|
||||||
|
|
||||||
|
public int defaultSizeX() { return cfg().getInt("default-size.x", 4); }
|
||||||
|
public int defaultSizeY() { return cfg().getInt("default-size.y", 4); }
|
||||||
|
public int defaultSizeZ() { return cfg().getInt("default-size.z", 6); }
|
||||||
|
|
||||||
|
public int maxSizeX() { return cfg().getInt("max-size.x", 16); }
|
||||||
|
public int maxSizeY() { return cfg().getInt("max-size.y", 8); }
|
||||||
|
public int maxSizeZ() { return cfg().getInt("max-size.z", 16); }
|
||||||
|
|
||||||
|
public double costSummon() { return cfg().getDouble("costs.summon", 100); }
|
||||||
|
public double costUnload() { return cfg().getDouble("costs.unload", 0); }
|
||||||
|
public double costUpgradeBase() { return cfg().getDouble("costs.upgrade-base", 1000); }
|
||||||
|
public double costSpaceJumpPerBlock(){ return cfg().getDouble("costs.space-jump-per-block", 1); }
|
||||||
|
|
||||||
|
public int spaceJumpBlocksPerLevel() { return cfg().getInt("space-jump.blocks-per-level", 100); }
|
||||||
|
public int spaceJumpMin() { return cfg().getInt("space-jump.min-blocks", 1); }
|
||||||
|
public int spaceJumpHardCap() { return cfg().getInt("space-jump.max-blocks-hard-cap", 5000); }
|
||||||
|
|
||||||
|
public int summonOffsetY() { return cfg().getInt("summon-offset-y", 30); }
|
||||||
|
|
||||||
|
public Material defaultPlatformMaterial() { return parseMat("default-platform-material", Material.SMOOTH_QUARTZ); }
|
||||||
|
public Material defaultEdgeMaterial() { return parseMat("default-edge-material", Material.QUARTZ_PILLAR); }
|
||||||
|
public Material defaultGlassMaterial() { return parseMat("default-glass-material", Material.WHITE_STAINED_GLASS); }
|
||||||
|
|
||||||
|
public String defaultShipName() { return cfg().getString("default-ship-name", "Spaceship #%n%"); }
|
||||||
|
|
||||||
|
public boolean checkCollision() { return cfg().getBoolean("safety.check-collision", true); }
|
||||||
|
public int minYAbovePlayer() { return cfg().getInt("safety.min-y-above-player", 10); }
|
||||||
|
|
||||||
|
private Material parseMat(String key, Material def) {
|
||||||
|
String s = cfg().getString(key);
|
||||||
|
if (s == null) return def;
|
||||||
|
Material m = Material.matchMaterial(s);
|
||||||
|
return m != null ? m : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ MESSAGES ============ */
|
||||||
|
|
||||||
|
public String prefix() { return color(cfg().getString("messages.prefix", "&8[&bSpaceShip&8] &7")); }
|
||||||
|
public String msgNoPerm() { return prefix() + color(cfg().getString("messages.no-permission", "&cNo perm")); }
|
||||||
|
public String msgNotOwner() { return prefix() + color(cfg().getString("messages.not-owner", "&cNot owner")); }
|
||||||
|
public String msgGiven(String n) { return prefix() + color(cfg().getString("messages.ship-given", "&aGiven")).replace("%name%", n); }
|
||||||
|
public String msgSummoned(String n) { return prefix() + color(cfg().getString("messages.ship-summoned", "&aSummoned")).replace("%name%", n); }
|
||||||
|
public String msgUnloaded(String n) { return prefix() + color(cfg().getString("messages.ship-unloaded", "&aUnloaded")).replace("%name%", n); }
|
||||||
|
public String msgTpUp(String n) { return prefix() + color(cfg().getString("messages.ship-teleport-up", "&aTP up")).replace("%name%", n); }
|
||||||
|
public String msgTpDown() { return prefix() + color(cfg().getString("messages.ship-teleport-down", "&aTP down")); }
|
||||||
|
public String msgAlreadyLoaded() { return prefix() + color(cfg().getString("messages.ship-already-loaded", "&eAlready loaded")); }
|
||||||
|
public String msgNotLoaded() { return prefix() + color(cfg().getString("messages.ship-not-loaded", "&eNot loaded")); }
|
||||||
|
public String msgCollision() { return prefix() + color(cfg().getString("messages.ship-collision", "&cCollision")); }
|
||||||
|
public String msgMaxShips(int max) { return prefix() + color(cfg().getString("messages.max-ships-reached", "&cMax")).replace("%max%", String.valueOf(max)); }
|
||||||
|
public String msgDeleted() { return prefix() + color(cfg().getString("messages.ship-deleted", "&cDeleted")); }
|
||||||
|
public String msgNotEnoughMoney(String need, String have) {
|
||||||
|
return prefix() + color(cfg().getString("messages.not-enough-money", "&cFonds insuffisants : %need%/%have%"))
|
||||||
|
.replace("%need%", need).replace("%have%", have);
|
||||||
|
}
|
||||||
|
public String msgVaultUnavailable() { return prefix() + color(cfg().getString("messages.vault-unavailable", "&cVault indisponible")); }
|
||||||
|
public String msgSpaceJumpSuccess(int dist, String dir, String cost) {
|
||||||
|
return prefix() + color(cfg().getString("messages.space-jump-success", "&aJump %dist% %dir% (-%cost%)"))
|
||||||
|
.replace("%dist%", String.valueOf(dist)).replace("%dir%", dir).replace("%cost%", cost);
|
||||||
|
}
|
||||||
|
public String msgSpaceJumpNotOnShip() { return prefix() + color(cfg().getString("messages.space-jump-not-on-ship", "&cPas sur le ship")); }
|
||||||
|
public String msgSpaceJumpTooFar(int max, int lvl) {
|
||||||
|
return prefix() + color(cfg().getString("messages.space-jump-too-far", "&cTrop loin (max %max% lvl %lvl%)"))
|
||||||
|
.replace("%max%", String.valueOf(max)).replace("%lvl%", String.valueOf(lvl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String color(String s) {
|
||||||
|
if (s == null) return "";
|
||||||
|
return s.replace('&', '\u00a7');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestionnaire de la base de données SQLite.
|
||||||
|
*
|
||||||
|
* Tables :
|
||||||
|
* - players(uuid, name, settings, created_at, updated_at)
|
||||||
|
* - spaceships(id, owner_uuid, name, size_x/y/z, spawn_x/y/z, schematic, settings, created_at, updated_at)
|
||||||
|
* - spaceships_loaded(spaceship_id, world, origin_x/y/z, loaded_at)
|
||||||
|
*/
|
||||||
|
public class DatabaseManager {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
public DatabaseManager(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
try {
|
||||||
|
File dataFolder = plugin.getDataFolder();
|
||||||
|
if (!dataFolder.exists()) {
|
||||||
|
dataFolder.mkdirs();
|
||||||
|
}
|
||||||
|
File dbFile = new File(dataFolder, "spaceships.db");
|
||||||
|
connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||||
|
try (Statement stmt = connection.createStatement()) {
|
||||||
|
stmt.execute("PRAGMA foreign_keys = ON;");
|
||||||
|
}
|
||||||
|
createTables();
|
||||||
|
plugin.getLogger().info("Base de donnees SpaceShipProject initialisee.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur a l'initialisation de la base : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
if (connection != null && !connection.isClosed()) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur a la fermeture de la base : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTables() throws SQLException {
|
||||||
|
String players = """
|
||||||
|
CREATE TABLE IF NOT EXISTS players (
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
name TEXT,
|
||||||
|
settings TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
String spaceships = """
|
||||||
|
CREATE TABLE IF NOT EXISTS spaceships (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
owner_uuid TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
size_x INTEGER NOT NULL,
|
||||||
|
size_y INTEGER NOT NULL,
|
||||||
|
size_z INTEGER NOT NULL,
|
||||||
|
spawn_x INTEGER NOT NULL,
|
||||||
|
spawn_y INTEGER NOT NULL,
|
||||||
|
spawn_z INTEGER NOT NULL,
|
||||||
|
schematic TEXT,
|
||||||
|
settings TEXT,
|
||||||
|
level INTEGER NOT NULL DEFAULT 1,
|
||||||
|
money INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_loaded_level INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (owner_uuid) REFERENCES players(uuid) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
String loaded = """
|
||||||
|
CREATE TABLE IF NOT EXISTS spaceships_loaded (
|
||||||
|
spaceship_id INTEGER PRIMARY KEY,
|
||||||
|
world TEXT NOT NULL,
|
||||||
|
origin_x INTEGER NOT NULL,
|
||||||
|
origin_y INTEGER NOT NULL,
|
||||||
|
origin_z INTEGER NOT NULL,
|
||||||
|
loaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (spaceship_id) REFERENCES spaceships(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
try (Statement stmt = connection.createStatement()) {
|
||||||
|
stmt.execute(players);
|
||||||
|
stmt.execute(spaceships);
|
||||||
|
stmt.execute(loaded);
|
||||||
|
// Migrations (idempotentes) pour les bases creees avant l'ajout de level/money.
|
||||||
|
tryAlter(stmt, "ALTER TABLE spaceships ADD COLUMN level INTEGER NOT NULL DEFAULT 1");
|
||||||
|
tryAlter(stmt, "ALTER TABLE spaceships ADD COLUMN money INTEGER NOT NULL DEFAULT 0");
|
||||||
|
tryAlter(stmt, "ALTER TABLE spaceships ADD COLUMN last_loaded_level INTEGER NOT NULL DEFAULT 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAlter(Statement stmt, String sql) {
|
||||||
|
try { stmt.execute(sql); } catch (SQLException ignored) { /* colonne deja presente */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================= PLAYERS ========================= */
|
||||||
|
|
||||||
|
public void upsertPlayer(Player player) {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO players(uuid, name, updated_at)
|
||||||
|
VALUES(?, ?, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(uuid) DO UPDATE SET name = excluded.name, updated_at = CURRENT_TIMESTAMP
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, player.getUniqueId().toString());
|
||||||
|
stmt.setString(2, player.getName());
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur upsertPlayer : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayerSettings(UUID uuid, String settingsJson) {
|
||||||
|
String sql = "UPDATE players SET settings = ?, updated_at = CURRENT_TIMESTAMP WHERE uuid = ?";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, settingsJson);
|
||||||
|
stmt.setString(2, uuid.toString());
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur setPlayerSettings : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlayerSettings(UUID uuid) {
|
||||||
|
String sql = "SELECT settings FROM players WHERE uuid = ?";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, uuid.toString());
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
if (rs.next()) return rs.getString("settings");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getPlayerSettings : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================= SPACESHIPS ========================= */
|
||||||
|
|
||||||
|
public long createSpaceship(UUID owner, String name,
|
||||||
|
int sizeX, int sizeY, int sizeZ,
|
||||||
|
int spawnX, int spawnY, int spawnZ,
|
||||||
|
String schematicJson, String settingsJson) {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO spaceships(owner_uuid, name, size_x, size_y, size_z,
|
||||||
|
spawn_x, spawn_y, spawn_z, schematic, settings, level, money, last_loaded_level)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, 1)
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||||
|
stmt.setString(1, owner.toString());
|
||||||
|
stmt.setString(2, name);
|
||||||
|
stmt.setInt(3, sizeX);
|
||||||
|
stmt.setInt(4, sizeY);
|
||||||
|
stmt.setInt(5, sizeZ);
|
||||||
|
stmt.setInt(6, spawnX);
|
||||||
|
stmt.setInt(7, spawnY);
|
||||||
|
stmt.setInt(8, spawnZ);
|
||||||
|
stmt.setString(9, schematicJson);
|
||||||
|
stmt.setString(10, settingsJson);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
try (ResultSet keys = stmt.getGeneratedKeys()) {
|
||||||
|
if (keys.next()) return keys.getLong(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur createSpaceship : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spaceship getSpaceship(long id) {
|
||||||
|
String sql = "SELECT * FROM spaceships WHERE id = ?";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setLong(1, id);
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
if (rs.next()) return rowToSpaceship(rs);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getSpaceship : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Spaceship> getSpaceshipsByOwner(UUID owner) {
|
||||||
|
List<Spaceship> list = new ArrayList<>();
|
||||||
|
String sql = "SELECT * FROM spaceships WHERE owner_uuid = ? ORDER BY id ASC";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, owner.toString());
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
while (rs.next()) list.add(rowToSpaceship(rs));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getSpaceshipsByOwner : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int countSpaceshipsByOwner(UUID owner) {
|
||||||
|
String sql = "SELECT COUNT(*) FROM spaceships WHERE owner_uuid = ?";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, owner.toString());
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
if (rs.next()) return rs.getInt(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur countSpaceshipsByOwner : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void updateSpaceship(Spaceship ship) {
|
||||||
|
String sql = """
|
||||||
|
UPDATE spaceships SET
|
||||||
|
name = ?, size_x = ?, size_y = ?, size_z = ?,
|
||||||
|
spawn_x = ?, spawn_y = ?, spawn_z = ?,
|
||||||
|
schematic = ?, settings = ?, level = ?, money = ?, last_loaded_level = ?,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, ship.getName());
|
||||||
|
stmt.setInt(2, ship.getSizeX());
|
||||||
|
stmt.setInt(3, ship.getSizeY());
|
||||||
|
stmt.setInt(4, ship.getSizeZ());
|
||||||
|
stmt.setInt(5, ship.getSpawnX());
|
||||||
|
stmt.setInt(6, ship.getSpawnY());
|
||||||
|
stmt.setInt(7, ship.getSpawnZ());
|
||||||
|
stmt.setString(8, ship.getSchematicJson());
|
||||||
|
stmt.setString(9, ship.getSettingsJson());
|
||||||
|
stmt.setInt(10, ship.getLevel());
|
||||||
|
stmt.setInt(11, ship.getMoney());
|
||||||
|
stmt.setInt(12, ship.getLastLoadedLevel());
|
||||||
|
stmt.setLong(13, ship.getId());
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur updateSpaceship : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSpaceship(long id) {
|
||||||
|
try (PreparedStatement st1 = connection.prepareStatement("DELETE FROM spaceships_loaded WHERE spaceship_id = ?");
|
||||||
|
PreparedStatement st2 = connection.prepareStatement("DELETE FROM spaceships WHERE id = ?")) {
|
||||||
|
st1.setLong(1, id);
|
||||||
|
st1.executeUpdate();
|
||||||
|
st2.setLong(1, id);
|
||||||
|
st2.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur deleteSpaceship : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spaceship rowToSpaceship(ResultSet rs) throws SQLException {
|
||||||
|
int level = 1, money = 0, lastLoadedLevel = 1;
|
||||||
|
try { level = rs.getInt("level"); if (rs.wasNull()) level = 1; } catch (SQLException ignored) {}
|
||||||
|
try { money = rs.getInt("money"); } catch (SQLException ignored) {}
|
||||||
|
try { lastLoadedLevel = rs.getInt("last_loaded_level"); if (rs.wasNull()) lastLoadedLevel = 1; } catch (SQLException ignored) {}
|
||||||
|
return new Spaceship(
|
||||||
|
rs.getLong("id"),
|
||||||
|
UUID.fromString(rs.getString("owner_uuid")),
|
||||||
|
rs.getString("name"),
|
||||||
|
rs.getInt("size_x"), rs.getInt("size_y"), rs.getInt("size_z"),
|
||||||
|
rs.getInt("spawn_x"), rs.getInt("spawn_y"), rs.getInt("spawn_z"),
|
||||||
|
rs.getString("schematic"),
|
||||||
|
rs.getString("settings"),
|
||||||
|
level, money, lastLoadedLevel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================= SPACESHIPS_LOADED ========================= */
|
||||||
|
|
||||||
|
public void markLoaded(long spaceshipId, String world, int x, int y, int z) {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO spaceships_loaded(spaceship_id, world, origin_x, origin_y, origin_z, loaded_at)
|
||||||
|
VALUES(?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(spaceship_id) DO UPDATE SET
|
||||||
|
world = excluded.world,
|
||||||
|
origin_x = excluded.origin_x,
|
||||||
|
origin_y = excluded.origin_y,
|
||||||
|
origin_z = excluded.origin_z,
|
||||||
|
loaded_at = CURRENT_TIMESTAMP
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setLong(1, spaceshipId);
|
||||||
|
stmt.setString(2, world);
|
||||||
|
stmt.setInt(3, x);
|
||||||
|
stmt.setInt(4, y);
|
||||||
|
stmt.setInt(5, z);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur markLoaded : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markUnloaded(long spaceshipId) {
|
||||||
|
String sql = "DELETE FROM spaceships_loaded WHERE spaceship_id = ?";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setLong(1, spaceshipId);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur markUnloaded : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoadedSpaceship getLoaded(long spaceshipId) {
|
||||||
|
String sql = """
|
||||||
|
SELECT l.spaceship_id, l.world, l.origin_x, l.origin_y, l.origin_z,
|
||||||
|
s.size_x, s.size_y, s.size_z
|
||||||
|
FROM spaceships_loaded l
|
||||||
|
JOIN spaceships s ON s.id = l.spaceship_id
|
||||||
|
WHERE l.spaceship_id = ?
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setLong(1, spaceshipId);
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return new LoadedSpaceship(
|
||||||
|
rs.getLong("spaceship_id"),
|
||||||
|
rs.getString("world"),
|
||||||
|
rs.getInt("origin_x"), rs.getInt("origin_y"), rs.getInt("origin_z"),
|
||||||
|
rs.getInt("size_x"), rs.getInt("size_y"), rs.getInt("size_z")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getLoaded : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LoadedSpaceship> getLoadedByOwner(UUID owner) {
|
||||||
|
List<LoadedSpaceship> list = new ArrayList<>();
|
||||||
|
String sql = """
|
||||||
|
SELECT l.spaceship_id, l.world, l.origin_x, l.origin_y, l.origin_z,
|
||||||
|
s.size_x, s.size_y, s.size_z
|
||||||
|
FROM spaceships_loaded l
|
||||||
|
JOIN spaceships s ON s.id = l.spaceship_id
|
||||||
|
WHERE s.owner_uuid = ?
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||||
|
stmt.setString(1, owner.toString());
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
list.add(new LoadedSpaceship(
|
||||||
|
rs.getLong("spaceship_id"),
|
||||||
|
rs.getString("world"),
|
||||||
|
rs.getInt("origin_x"), rs.getInt("origin_y"), rs.getInt("origin_z"),
|
||||||
|
rs.getInt("size_x"), rs.getInt("size_y"), rs.getInt("size_z")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getLoadedByOwner : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LoadedSpaceship> getAllLoaded() {
|
||||||
|
List<LoadedSpaceship> list = new ArrayList<>();
|
||||||
|
String sql = """
|
||||||
|
SELECT l.spaceship_id, l.world, l.origin_x, l.origin_y, l.origin_z,
|
||||||
|
s.size_x, s.size_y, s.size_z
|
||||||
|
FROM spaceships_loaded l
|
||||||
|
JOIN spaceships s ON s.id = l.spaceship_id
|
||||||
|
""";
|
||||||
|
try (PreparedStatement stmt = connection.prepareStatement(sql);
|
||||||
|
ResultSet rs = stmt.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
list.add(new LoadedSpaceship(
|
||||||
|
rs.getLong("spaceship_id"),
|
||||||
|
rs.getString("world"),
|
||||||
|
rs.getInt("origin_x"), rs.getInt("origin_y"), rs.getInt("origin_z"),
|
||||||
|
rs.getInt("size_x"), rs.getInt("size_y"), rs.getInt("size_z")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Erreur getAllLoaded : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import net.milkbowl.vault.economy.Economy;
|
||||||
|
import net.milkbowl.vault.economy.EconomyResponse;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper autour de Vault. La seule source d'argent du plugin est l'economie expose par Vault
|
||||||
|
* (EssentialsEconomy par defaut). Aucune monnaie n'est stockee dans la base du plugin.
|
||||||
|
*/
|
||||||
|
public class EconomyHook {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private Economy economy;
|
||||||
|
|
||||||
|
public EconomyHook(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialise l'integration Vault. Retourne true si OK. */
|
||||||
|
public boolean setup() {
|
||||||
|
if (plugin.getServer().getPluginManager().getPlugin("Vault") == null) {
|
||||||
|
plugin.getLogger().warning("Vault est introuvable : les fonctionnalites economiques seront desactivees.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RegisteredServiceProvider<Economy> rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class);
|
||||||
|
if (rsp == null) {
|
||||||
|
plugin.getLogger().warning("Aucun fournisseur d'economie Vault detecte (EssentialsEconomy ?). Economie desactivee.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.economy = rsp.getProvider();
|
||||||
|
plugin.getLogger().info("Vault detecte, fournisseur : " + economy.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReady() { return economy != null; }
|
||||||
|
|
||||||
|
public double getBalance(UUID uuid) {
|
||||||
|
if (!isReady()) return 0;
|
||||||
|
OfflinePlayer p = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
return economy.getBalance(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has(UUID uuid, double amount) {
|
||||||
|
if (!isReady()) return false;
|
||||||
|
OfflinePlayer p = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
return economy.has(p, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retire `amount` au joueur. True si succes. */
|
||||||
|
public boolean withdraw(UUID uuid, double amount) {
|
||||||
|
if (!isReady()) return false;
|
||||||
|
if (amount <= 0) return true;
|
||||||
|
OfflinePlayer p = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
EconomyResponse r = economy.withdrawPlayer(p, amount);
|
||||||
|
return r.transactionSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Depose `amount` au joueur. True si succes. */
|
||||||
|
public boolean deposit(UUID uuid, double amount) {
|
||||||
|
if (!isReady()) return false;
|
||||||
|
if (amount <= 0) return true;
|
||||||
|
OfflinePlayer p = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
EconomyResponse r = economy.depositPlayer(p, amount);
|
||||||
|
return r.transactionSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(double amount) {
|
||||||
|
if (!isReady()) return String.valueOf(amount);
|
||||||
|
return economy.format(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Représente un spaceship actuellement chargé dans le monde.
|
||||||
|
* Stocké en table spaceships_loaded.
|
||||||
|
*/
|
||||||
|
public class LoadedSpaceship {
|
||||||
|
|
||||||
|
private final long spaceshipId;
|
||||||
|
private final String worldName;
|
||||||
|
private final int originX;
|
||||||
|
private final int originY;
|
||||||
|
private final int originZ;
|
||||||
|
private final int sizeX;
|
||||||
|
private final int sizeY;
|
||||||
|
private final int sizeZ;
|
||||||
|
|
||||||
|
public LoadedSpaceship(long spaceshipId, String worldName,
|
||||||
|
int originX, int originY, int originZ,
|
||||||
|
int sizeX, int sizeY, int sizeZ) {
|
||||||
|
this.spaceshipId = spaceshipId;
|
||||||
|
this.worldName = worldName;
|
||||||
|
this.originX = originX;
|
||||||
|
this.originY = originY;
|
||||||
|
this.originZ = originZ;
|
||||||
|
this.sizeX = sizeX;
|
||||||
|
this.sizeY = sizeY;
|
||||||
|
this.sizeZ = sizeZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSpaceshipId() { return spaceshipId; }
|
||||||
|
public String getWorldName() { return worldName; }
|
||||||
|
public int getOriginX() { return originX; }
|
||||||
|
public int getOriginY() { return originY; }
|
||||||
|
public int getOriginZ() { return originZ; }
|
||||||
|
public int getSizeX() { return sizeX; }
|
||||||
|
public int getSizeY() { return sizeY; }
|
||||||
|
public int getSizeZ() { return sizeZ; }
|
||||||
|
|
||||||
|
public World getWorld() {
|
||||||
|
return Bukkit.getWorld(worldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoundingBox getBoundingBox() {
|
||||||
|
return new BoundingBox(originX, originY, originZ,
|
||||||
|
originX + sizeX, originY + sizeY, originZ + sizeZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Location loc) {
|
||||||
|
if (loc == null || loc.getWorld() == null) return false;
|
||||||
|
if (!loc.getWorld().getName().equals(worldName)) return false;
|
||||||
|
return getBoundingBox().contains(loc.toVector());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gere les modeles (templates) de spaceship :
|
||||||
|
* - lecture / ecriture des schematiques modeles sur disque (dossier "models/")
|
||||||
|
* - sessions d'edition par admin (loadModel / saveModel)
|
||||||
|
*
|
||||||
|
* Le modele "default.json" est utilise par /ss create quand il existe.
|
||||||
|
*/
|
||||||
|
public class ModelManager {
|
||||||
|
|
||||||
|
private static final String DEFAULT = "default";
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final File modelsDir;
|
||||||
|
|
||||||
|
/** Sessions d'edition actives : adminUuid -> session. */
|
||||||
|
private final Map<UUID, ModelEditSession> sessions = new HashMap<>();
|
||||||
|
|
||||||
|
public ModelManager(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.modelsDir = new File(plugin.getDataFolder(), "models");
|
||||||
|
if (!modelsDir.exists()) modelsDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Recharge depuis disque (ne fait rien d'autre, lecture a la demande). */
|
||||||
|
public void reload() { /* no-op : on lit a la demande */ }
|
||||||
|
|
||||||
|
/** Charge un modele du disque, ou null si absent. */
|
||||||
|
public String loadModelJson(String name) {
|
||||||
|
File file = new File(modelsDir, sanitize(name) + ".json");
|
||||||
|
if (!file.exists()) return null;
|
||||||
|
try {
|
||||||
|
return Files.readString(file.toPath(), StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Lecture modele " + name + " : " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ecrit un modele sur disque. */
|
||||||
|
public boolean saveModelJson(String name, String json) {
|
||||||
|
File file = new File(modelsDir, sanitize(name) + ".json");
|
||||||
|
try {
|
||||||
|
Files.writeString(file.toPath(), json, StandardCharsets.UTF_8);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Ecriture modele " + name + " : " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Charge le modele "default", ou null si absent. */
|
||||||
|
public String loadDefaultModelJson() { return loadModelJson(DEFAULT); }
|
||||||
|
|
||||||
|
/** True si "default.json" est present sur disque. */
|
||||||
|
public boolean hasDefaultModel() { return loadDefaultModelJson() != null; }
|
||||||
|
|
||||||
|
/* ============================ EDIT SESSIONS ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demarre une session d'edition : place la schematique du modele dans le monde devant l'admin.
|
||||||
|
* Retourne true si OK.
|
||||||
|
*/
|
||||||
|
public boolean loadForEdit(Player admin, String modelName) {
|
||||||
|
if (sessions.containsKey(admin.getUniqueId())) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cVous avez deja une session d'edition. /ssa savemodel ou /ssa cancelmodel."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
String json = loadModelJson(modelName);
|
||||||
|
if (json == null) {
|
||||||
|
// Generer un modele initial a partir de la config par defaut.
|
||||||
|
json = SchematicHelper.buildDefaultSchematic(
|
||||||
|
cfg.defaultSizeX(), cfg.defaultSizeY(), cfg.defaultSizeZ(),
|
||||||
|
cfg.defaultPlatformMaterial(),
|
||||||
|
cfg.defaultEdgeMaterial(),
|
||||||
|
cfg.defaultGlassMaterial());
|
||||||
|
}
|
||||||
|
int[] size = SchematicHelper.readSize(json);
|
||||||
|
World world = admin.getWorld();
|
||||||
|
int ox = admin.getLocation().getBlockX() - size[0] / 2;
|
||||||
|
int oz = admin.getLocation().getBlockZ() - size[2] / 2;
|
||||||
|
int oy = admin.getLocation().getBlockY() + cfg.summonOffsetY();
|
||||||
|
// Empeche d'ecraser des spaceships charges.
|
||||||
|
if (SchematicHelper.hasCollision(world, ox, oy, oz, size[0], size[1], size[2])) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cZone obstruee, deplacez-vous puis reessayez."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SchematicHelper.restoreRegion(json, world, ox, oy, oz);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cErreur restauration modele : " + ex.getMessage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sessions.put(admin.getUniqueId(),
|
||||||
|
new ModelEditSession(modelName, world.getName(), ox, oy, oz, size[0], size[1], size[2]));
|
||||||
|
admin.sendMessage(ConfigManager.color("&aModele &b" + modelName + " &acharge en x=" + ox + " y=" + oy + " z=" + oz + ". Editez puis /ssa savemodel."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capture la session active de l'admin et l'ecrit sur disque. */
|
||||||
|
public boolean saveEdit(Player admin) {
|
||||||
|
ModelEditSession s = sessions.get(admin.getUniqueId());
|
||||||
|
if (s == null) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cAucune session d'edition active."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
World world = plugin.getServer().getWorld(s.worldName);
|
||||||
|
if (world == null) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cMonde introuvable."));
|
||||||
|
sessions.remove(admin.getUniqueId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Spawn = milieu sur le sol +1, comme le defaut, mais conserve celui du modele original si present.
|
||||||
|
int spawnX = s.sizeX / 2, spawnY = 1, spawnZ = s.sizeZ / 2;
|
||||||
|
String existing = loadModelJson(s.modelName);
|
||||||
|
if (existing != null) {
|
||||||
|
try {
|
||||||
|
int[] sp = SchematicHelper.readSpawn(existing);
|
||||||
|
spawnX = sp[0]; spawnY = sp[1]; spawnZ = sp[2];
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
String json = SchematicHelper.captureRegion(world, s.originX, s.originY, s.originZ,
|
||||||
|
s.sizeX, s.sizeY, s.sizeZ, spawnX, spawnY, spawnZ);
|
||||||
|
boolean ok = saveModelJson(s.modelName, json);
|
||||||
|
if (ok) {
|
||||||
|
SchematicHelper.clearRegion(world, s.originX, s.originY, s.originZ, s.sizeX, s.sizeY, s.sizeZ);
|
||||||
|
sessions.remove(admin.getUniqueId());
|
||||||
|
admin.sendMessage(ConfigManager.color("&aModele &b" + s.modelName + " &asauvegarde."));
|
||||||
|
} else {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cErreur d'ecriture du modele."));
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Annule la session : nettoie la zone et oublie la session. */
|
||||||
|
public boolean cancelEdit(Player admin) {
|
||||||
|
ModelEditSession s = sessions.remove(admin.getUniqueId());
|
||||||
|
if (s == null) {
|
||||||
|
admin.sendMessage(ConfigManager.color("&cAucune session active."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
World world = plugin.getServer().getWorld(s.worldName);
|
||||||
|
if (world != null) {
|
||||||
|
SchematicHelper.clearRegion(world, s.originX, s.originY, s.originZ, s.sizeX, s.sizeY, s.sizeZ);
|
||||||
|
}
|
||||||
|
admin.sendMessage(ConfigManager.color("&7Session d'edition annulee."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelEditSession getSession(UUID admin) { return sessions.get(admin); }
|
||||||
|
|
||||||
|
/** Decharge toutes les sessions au shutdown (sauvegarde best-effort + nettoyage). */
|
||||||
|
public void unloadAllOnShutdown() {
|
||||||
|
for (Map.Entry<UUID, ModelEditSession> entry : sessions.entrySet()) {
|
||||||
|
ModelEditSession s = entry.getValue();
|
||||||
|
World world = plugin.getServer().getWorld(s.worldName);
|
||||||
|
if (world != null) {
|
||||||
|
try {
|
||||||
|
String json = SchematicHelper.captureRegion(world, s.originX, s.originY, s.originZ,
|
||||||
|
s.sizeX, s.sizeY, s.sizeZ, s.sizeX / 2, 1, s.sizeZ / 2);
|
||||||
|
saveModelJson(s.modelName, json);
|
||||||
|
SchematicHelper.clearRegion(world, s.originX, s.originY, s.originZ, s.sizeX, s.sizeY, s.sizeZ);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().warning("Erreur shutdown session modele : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sanitize(String name) {
|
||||||
|
if (name == null || name.isBlank()) return DEFAULT;
|
||||||
|
return name.toLowerCase().replaceAll("[^a-z0-9_-]", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Petit conteneur immuable. */
|
||||||
|
public static final class ModelEditSession {
|
||||||
|
public final String modelName;
|
||||||
|
public final String worldName;
|
||||||
|
public final int originX, originY, originZ;
|
||||||
|
public final int sizeX, sizeY, sizeZ;
|
||||||
|
|
||||||
|
public ModelEditSession(String modelName, String worldName,
|
||||||
|
int originX, int originY, int originZ,
|
||||||
|
int sizeX, int sizeY, int sizeZ) {
|
||||||
|
this.modelName = modelName;
|
||||||
|
this.worldName = worldName;
|
||||||
|
this.originX = originX; this.originY = originY; this.originZ = originZ;
|
||||||
|
this.sizeX = sizeX; this.sizeY = sizeY; this.sizeZ = sizeZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Connexion : insere/maj le joueur dans la table players, nettoie ses telecommandes.
|
||||||
|
* - Deconnexion : decharge tous ses spaceships (sauvegarde les modifications + le contenu des coffres).
|
||||||
|
*/
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
/** Memorise le ship sur lequel le joueur est mort, pour respawn. */
|
||||||
|
private final Map<UUID, Long> deathOnShip = new HashMap<>();
|
||||||
|
|
||||||
|
public PlayerListener(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
plugin.getDatabaseManager().upsertPlayer(player);
|
||||||
|
int removed = plugin.getSpaceshipManager().sanitizePlayerRemotes(player);
|
||||||
|
if (removed > 0) {
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&7" + removed + " telecommande(s) invalide(s) ont ete supprimees."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onDeath(PlayerDeathEvent event) {
|
||||||
|
Player player = event.getEntity();
|
||||||
|
// Cherche un spaceship du joueur dont la BB contient la position du deces.
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
if (loc.getWorld() == null) return;
|
||||||
|
// Verifie d'abord les ships dont le joueur est proprietaire et qui sont charges.
|
||||||
|
for (LoadedSpaceship loaded : plugin.getDatabaseManager().getLoadedByOwner(player.getUniqueId())) {
|
||||||
|
if (loaded.contains(loc)) {
|
||||||
|
deathOnShip.put(player.getUniqueId(), loaded.getSpaceshipId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback : premier ship charge du joueur, le cas echeant.
|
||||||
|
List<LoadedSpaceship> all = plugin.getDatabaseManager().getLoadedByOwner(player.getUniqueId());
|
||||||
|
if (!all.isEmpty()) deathOnShip.put(player.getUniqueId(), all.get(0).getSpaceshipId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH)
|
||||||
|
public void onRespawn(PlayerRespawnEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
Long shipId = deathOnShip.remove(player.getUniqueId());
|
||||||
|
if (shipId == null) return;
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(shipId);
|
||||||
|
if (ship == null) return;
|
||||||
|
LoadedSpaceship loaded = plugin.getDatabaseManager().getLoaded(shipId);
|
||||||
|
if (loaded == null) return;
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) return;
|
||||||
|
Location target = new Location(world,
|
||||||
|
loaded.getOriginX() + ship.getSpawnX() + 0.5,
|
||||||
|
loaded.getOriginY() + ship.getSpawnY(),
|
||||||
|
loaded.getOriginZ() + ship.getSpawnZ() + 0.5);
|
||||||
|
event.setRespawnLocation(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
try {
|
||||||
|
// Si le joueur est sur l'un de ses ships, le tp au sol avant de decharger
|
||||||
|
// (pour qu'il ne reapparaisse pas en plein ciel a sa prochaine connexion).
|
||||||
|
Spaceship onShip = plugin.getSpaceshipManager().findOwnedShipPlayerIsOn(player);
|
||||||
|
if (onShip != null) {
|
||||||
|
plugin.getSpaceshipManager().teleportDown(player, onShip);
|
||||||
|
}
|
||||||
|
plugin.getSpaceshipManager().unloadAllForPlayer(player.getUniqueId());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().warning("Erreur quit pour " + player.getName() + " : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.Action;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gere les interactions avec la telecommande boussole.
|
||||||
|
*
|
||||||
|
* - Clic gauche
|
||||||
|
* - ship non charge -> invoquer
|
||||||
|
* - ship charge && joueur PAS dessus -> decharger (sauvegarde les modifications)
|
||||||
|
* - ship charge && joueur dessus -> rien (utilise clic droit pour redescendre)
|
||||||
|
*
|
||||||
|
* - Clic droit
|
||||||
|
* - ship non charge -> invoquer puis teleporter
|
||||||
|
* - ship charge && joueur PAS dessus -> teleporter sur le ship
|
||||||
|
* - ship charge && joueur dessus -> redescendre au sol
|
||||||
|
*
|
||||||
|
* - Shift + clic droit -> ouvrir le GUI parametres
|
||||||
|
*/
|
||||||
|
public class RemoteListener implements Listener {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final SpaceshipManager manager;
|
||||||
|
private final RemoteManager remotes;
|
||||||
|
private final ConfigManager cfg;
|
||||||
|
|
||||||
|
public RemoteListener(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.manager = plugin.getSpaceshipManager();
|
||||||
|
this.remotes = plugin.getRemoteManager();
|
||||||
|
this.cfg = plugin.getConfigManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = false)
|
||||||
|
public void onInteract(PlayerInteractEvent event) {
|
||||||
|
if (event.getHand() != EquipmentSlot.HAND) return;
|
||||||
|
|
||||||
|
ItemStack item = event.getItem();
|
||||||
|
if (item == null || !remotes.isRemote(item)) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
long shipId = remotes.getShipId(item);
|
||||||
|
if (shipId <= 0) return;
|
||||||
|
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(shipId);
|
||||||
|
if (ship == null) {
|
||||||
|
player.sendMessage(cfg.prefix() + ConfigManager.color("&cCe spaceship n'existe plus, telecommande retiree."));
|
||||||
|
// Retire la telecommande obsolete pour eviter le spam.
|
||||||
|
int slot = player.getInventory().getHeldItemSlot();
|
||||||
|
ItemStack mainHand = player.getInventory().getItemInMainHand();
|
||||||
|
if (plugin.getRemoteManager().isRemote(mainHand)
|
||||||
|
&& plugin.getRemoteManager().getShipId(mainHand) == shipId) {
|
||||||
|
player.getInventory().setItem(slot, null);
|
||||||
|
}
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Securite : seul le proprietaire (ou un admin) peut utiliser la telecommande.
|
||||||
|
if (!ship.getOwnerUuid().equals(player.getUniqueId())
|
||||||
|
&& !player.hasPermission("spaceship.admin")) {
|
||||||
|
player.sendMessage(cfg.msgNotOwner());
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Action action = event.getAction();
|
||||||
|
|
||||||
|
// Shift + clic droit -> GUI parametres
|
||||||
|
if (player.isSneaking()
|
||||||
|
&& (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
new SpaceshipGUI(plugin, player, ship).open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clic gauche
|
||||||
|
if (action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
handleLeftClick(player, ship);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clic droit
|
||||||
|
if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
handleRightClick(player, ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLeftClick(Player player, Spaceship ship) {
|
||||||
|
boolean loaded = manager.isLoaded(ship.getId());
|
||||||
|
if (!loaded) {
|
||||||
|
manager.summon(player, ship);
|
||||||
|
} else if (manager.isPlayerOnShip(player, ship)) {
|
||||||
|
// Sur le ship -> on n'unload pas (le joueur tomberait), on indique la marche a suivre
|
||||||
|
player.sendMessage(cfg.prefix() + ConfigManager.color(
|
||||||
|
"&eUtilisez le clic droit pour redescendre avant de decharger."));
|
||||||
|
} else {
|
||||||
|
manager.unload(player, ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRightClick(Player player, Spaceship ship) {
|
||||||
|
boolean loaded = manager.isLoaded(ship.getId());
|
||||||
|
if (!loaded) {
|
||||||
|
// Invoque puis teleporte
|
||||||
|
if (manager.summon(player, ship)) {
|
||||||
|
manager.teleportToShip(player, ship);
|
||||||
|
}
|
||||||
|
} else if (manager.isPlayerOnShip(player, ship)) {
|
||||||
|
manager.teleportDown(player, ship);
|
||||||
|
} else {
|
||||||
|
manager.teleportToShip(player, ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cree, identifie et met a jour les boussoles "telecommande" de spaceship.
|
||||||
|
* Chaque boussole porte dans son PersistentDataContainer l'id du spaceship associe.
|
||||||
|
*/
|
||||||
|
public class RemoteManager {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final NamespacedKey shipIdKey;
|
||||||
|
private final NamespacedKey markerKey;
|
||||||
|
|
||||||
|
public RemoteManager(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.shipIdKey = new NamespacedKey(plugin, "spaceship_id");
|
||||||
|
this.markerKey = new NamespacedKey(plugin, "spaceship_remote");
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamespacedKey getShipIdKey() { return shipIdKey; }
|
||||||
|
public NamespacedKey getMarkerKey() { return markerKey; }
|
||||||
|
|
||||||
|
/** True si l'item est une telecommande SpaceShipProject. */
|
||||||
|
public boolean isRemote(ItemStack item) {
|
||||||
|
if (item == null || item.getType() != Material.RECOVERY_COMPASS) return false;
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta == null) return false;
|
||||||
|
return meta.getPersistentDataContainer().has(markerKey, PersistentDataType.BYTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lit l'id du spaceship associe a la telecommande, ou -1. */
|
||||||
|
public long getShipId(ItemStack item) {
|
||||||
|
if (!isRemote(item)) return -1;
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
Long id = meta.getPersistentDataContainer().get(shipIdKey, PersistentDataType.LONG);
|
||||||
|
return id == null ? -1 : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Construit une telecommande pour le spaceship donne. */
|
||||||
|
public ItemStack buildRemote(Spaceship ship) {
|
||||||
|
ItemStack item = new ItemStack(Material.RECOVERY_COMPASS);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta == null) return item;
|
||||||
|
|
||||||
|
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||||
|
pdc.set(markerKey, PersistentDataType.BYTE, (byte) 1);
|
||||||
|
pdc.set(shipIdKey, PersistentDataType.LONG, ship.getId());
|
||||||
|
|
||||||
|
applyDisplay(meta, ship);
|
||||||
|
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconstruit la telecommande sur place (apres renommage par exemple).
|
||||||
|
* Conserve la quantite et l'emplacement de l'item.
|
||||||
|
*/
|
||||||
|
public void refreshRemote(ItemStack item, Spaceship ship) {
|
||||||
|
if (!isRemote(item) || ship == null) return;
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta == null) return;
|
||||||
|
applyDisplay(meta, ship);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDisplay(ItemMeta meta, Spaceship ship) {
|
||||||
|
meta.setDisplayName(ConfigManager.color("&b&l\u2730 &fTelecommande &7- &b" + ship.getName()));
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ConfigManager.color("&7ID : &f#" + ship.getId()));
|
||||||
|
lore.add(ConfigManager.color("&7Niveau : &e" + ship.getLevel()));
|
||||||
|
lore.add(ConfigManager.color("&7Taille : &f" + ship.getSizeX() + "x" + ship.getSizeY() + "x" + ship.getSizeZ()));
|
||||||
|
lore.add("");
|
||||||
|
lore.add(ConfigManager.color("&eClic gauche &8: &7invoquer / decharger"));
|
||||||
|
lore.add(ConfigManager.color("&eClic droit &8: &7monter / redescendre"));
|
||||||
|
lore.add(ConfigManager.color("&eShift + clic droit &8: &7parametres"));
|
||||||
|
meta.setLore(lore);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.Container;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture / restauration / nettoyage / generation de schematiques compactes (palette + RLE)
|
||||||
|
* stockees en JSON pour le plugin SpaceShipProject.
|
||||||
|
*
|
||||||
|
* Format JSON :
|
||||||
|
* {
|
||||||
|
* "size":[sx,sy,sz],
|
||||||
|
* "spawn":[ox,oy,oz],
|
||||||
|
* "palette":["minecraft:air","minecraft:smooth_quartz", ...],
|
||||||
|
* "blocks":[[paletteIndex, runLength], ...],
|
||||||
|
* "containers":[
|
||||||
|
* {"x":1,"y":2,"z":3,"items":[{"slot":0,"data":"base64..."}, ...]}
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public final class SchematicHelper {
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create();
|
||||||
|
private static final String AIR = "minecraft:air";
|
||||||
|
|
||||||
|
private SchematicHelper() {}
|
||||||
|
|
||||||
|
/* ============================ CAPTURE ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture la region [origin, origin+size) du monde dans une representation JSON compacte.
|
||||||
|
* Conserve le spawn relatif (pour ne pas le perdre lors d'un re-load).
|
||||||
|
*/
|
||||||
|
public static String captureRegion(World world,
|
||||||
|
int originX, int originY, int originZ,
|
||||||
|
int sizeX, int sizeY, int sizeZ,
|
||||||
|
int spawnX, int spawnY, int spawnZ) {
|
||||||
|
Map<String, Integer> paletteIndex = new LinkedHashMap<>();
|
||||||
|
List<String> palette = new ArrayList<>();
|
||||||
|
List<int[]> blocks = new ArrayList<>();
|
||||||
|
JsonArray containers = new JsonArray();
|
||||||
|
|
||||||
|
int currentIdx = -1;
|
||||||
|
int run = 0;
|
||||||
|
|
||||||
|
for (int y = 0; y < sizeY; y++) {
|
||||||
|
for (int z = 0; z < sizeZ; z++) {
|
||||||
|
for (int x = 0; x < sizeX; x++) {
|
||||||
|
Block b = world.getBlockAt(originX + x, originY + y, originZ + z);
|
||||||
|
String state = b.getBlockData().getAsString();
|
||||||
|
Integer idx = paletteIndex.get(state);
|
||||||
|
if (idx == null) {
|
||||||
|
idx = palette.size();
|
||||||
|
palette.add(state);
|
||||||
|
paletteIndex.put(state, idx);
|
||||||
|
}
|
||||||
|
if (idx == currentIdx) {
|
||||||
|
run++;
|
||||||
|
} else {
|
||||||
|
if (currentIdx != -1) blocks.add(new int[]{currentIdx, run});
|
||||||
|
currentIdx = idx;
|
||||||
|
run = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture du contenu des conteneurs (coffres, barils, fournaises, etc.)
|
||||||
|
BlockState bs = b.getState(false);
|
||||||
|
if (bs instanceof Container container) {
|
||||||
|
JsonObject entry = serializeContainer(container, x, y, z);
|
||||||
|
if (entry != null) containers.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (run > 0) blocks.add(new int[]{currentIdx, run});
|
||||||
|
|
||||||
|
JsonObject root = new JsonObject();
|
||||||
|
JsonArray sizeArr = new JsonArray();
|
||||||
|
sizeArr.add(sizeX); sizeArr.add(sizeY); sizeArr.add(sizeZ);
|
||||||
|
root.add("size", sizeArr);
|
||||||
|
|
||||||
|
JsonArray spawnArr = new JsonArray();
|
||||||
|
spawnArr.add(spawnX); spawnArr.add(spawnY); spawnArr.add(spawnZ);
|
||||||
|
root.add("spawn", spawnArr);
|
||||||
|
|
||||||
|
JsonArray paletteArr = new JsonArray();
|
||||||
|
for (String p : palette) paletteArr.add(p);
|
||||||
|
root.add("palette", paletteArr);
|
||||||
|
|
||||||
|
JsonArray blocksArr = new JsonArray();
|
||||||
|
for (int[] run2 : blocks) {
|
||||||
|
JsonArray ja = new JsonArray();
|
||||||
|
ja.add(run2[0]);
|
||||||
|
ja.add(run2[1]);
|
||||||
|
blocksArr.add(ja);
|
||||||
|
}
|
||||||
|
root.add("blocks", blocksArr);
|
||||||
|
|
||||||
|
if (containers.size() > 0) root.add("containers", containers);
|
||||||
|
|
||||||
|
return GSON.toJson(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonObject serializeContainer(Container container, int x, int y, int z) {
|
||||||
|
Inventory inv = container.getSnapshotInventory();
|
||||||
|
if (inv == null) return null;
|
||||||
|
JsonArray items = new JsonArray();
|
||||||
|
for (int slot = 0; slot < inv.getSize(); slot++) {
|
||||||
|
ItemStack stack = inv.getItem(slot);
|
||||||
|
if (stack == null || stack.getType().isAir()) continue;
|
||||||
|
JsonObject e = new JsonObject();
|
||||||
|
e.addProperty("slot", slot);
|
||||||
|
e.addProperty("data", Base64.getEncoder().encodeToString(stack.serializeAsBytes()));
|
||||||
|
items.add(e);
|
||||||
|
}
|
||||||
|
if (items.size() == 0) return null;
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("x", x);
|
||||||
|
obj.addProperty("y", y);
|
||||||
|
obj.addProperty("z", z);
|
||||||
|
obj.add("items", items);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ RESTORE ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restaure une schematique JSON dans [origin, origin+size).
|
||||||
|
* Les blocs "minecraft:air" sont remplaces par de l'air (efface ce qui etait la).
|
||||||
|
*/
|
||||||
|
public static void restoreRegion(String json, World world,
|
||||||
|
int originX, int originY, int originZ) {
|
||||||
|
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
JsonArray sizeArr = root.getAsJsonArray("size");
|
||||||
|
int sizeX = sizeArr.get(0).getAsInt();
|
||||||
|
int sizeY = sizeArr.get(1).getAsInt();
|
||||||
|
int sizeZ = sizeArr.get(2).getAsInt();
|
||||||
|
|
||||||
|
JsonArray paletteArr = root.getAsJsonArray("palette");
|
||||||
|
BlockData[] palette = new BlockData[paletteArr.size()];
|
||||||
|
for (int i = 0; i < paletteArr.size(); i++) {
|
||||||
|
String state = paletteArr.get(i).getAsString();
|
||||||
|
try {
|
||||||
|
palette[i] = Bukkit.createBlockData(state);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
palette[i] = Bukkit.createBlockData(Material.AIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray blocksArr = root.getAsJsonArray("blocks");
|
||||||
|
int cursor = 0; // index dans l'ordre Y -> Z -> X
|
||||||
|
int total = sizeX * sizeY * sizeZ;
|
||||||
|
for (int i = 0; i < blocksArr.size() && cursor < total; i++) {
|
||||||
|
JsonArray pair = blocksArr.get(i).getAsJsonArray();
|
||||||
|
int idx = pair.get(0).getAsInt();
|
||||||
|
int len = pair.get(1).getAsInt();
|
||||||
|
BlockData data = palette[idx];
|
||||||
|
for (int j = 0; j < len && cursor < total; j++, cursor++) {
|
||||||
|
int x = cursor % sizeX;
|
||||||
|
int z = (cursor / sizeX) % sizeZ;
|
||||||
|
int y = cursor / (sizeX * sizeZ);
|
||||||
|
Block b = world.getBlockAt(originX + x, originY + y, originZ + z);
|
||||||
|
b.setBlockData(data, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restauration du contenu des conteneurs (apres avoir pose les blocs).
|
||||||
|
JsonElement contEl = root.get("containers");
|
||||||
|
if (contEl != null && contEl.isJsonArray()) {
|
||||||
|
for (JsonElement el : contEl.getAsJsonArray()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
int x = obj.get("x").getAsInt();
|
||||||
|
int y = obj.get("y").getAsInt();
|
||||||
|
int z = obj.get("z").getAsInt();
|
||||||
|
Block b = world.getBlockAt(originX + x, originY + y, originZ + z);
|
||||||
|
BlockState bs = b.getState(false);
|
||||||
|
if (!(bs instanceof Container container)) continue;
|
||||||
|
Inventory inv = container.getInventory();
|
||||||
|
if (inv == null) continue;
|
||||||
|
JsonArray items = obj.getAsJsonArray("items");
|
||||||
|
for (JsonElement itemEl : items) {
|
||||||
|
JsonObject itemObj = itemEl.getAsJsonObject();
|
||||||
|
int slot = itemObj.get("slot").getAsInt();
|
||||||
|
String b64 = itemObj.get("data").getAsString();
|
||||||
|
try {
|
||||||
|
byte[] bytes = Base64.getDecoder().decode(b64);
|
||||||
|
ItemStack stack = ItemStack.deserializeBytes(bytes);
|
||||||
|
if (slot >= 0 && slot < inv.getSize()) inv.setItem(slot, stack);
|
||||||
|
} catch (IllegalArgumentException ignored) { /* item invalide */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] readSize(String json) {
|
||||||
|
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
JsonArray sz = root.getAsJsonArray("size");
|
||||||
|
return new int[]{sz.get(0).getAsInt(), sz.get(1).getAsInt(), sz.get(2).getAsInt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] readSpawn(String json) {
|
||||||
|
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
JsonArray sp = root.getAsJsonArray("spawn");
|
||||||
|
return new int[]{sp.get(0).getAsInt(), sp.get(1).getAsInt(), sp.get(2).getAsInt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ EXPANSION ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Etend la schematique a la nouvelle taille (les dimensions doivent etre >= a l'ancienne).
|
||||||
|
* Les blocs ajoutes sont de l'air. Le spawn est conserve.
|
||||||
|
* Si {@code markCornerMaterial} != null, place ce materiau aux 8 coins de la nouvelle boite
|
||||||
|
* pour signaler visuellement les nouvelles limites.
|
||||||
|
*/
|
||||||
|
public static String expandSchematic(String oldJson, int newX, int newY, int newZ,
|
||||||
|
Material markCornerMaterial) {
|
||||||
|
JsonObject root = JsonParser.parseString(oldJson).getAsJsonObject();
|
||||||
|
JsonArray oldSize = root.getAsJsonArray("size");
|
||||||
|
int ox = oldSize.get(0).getAsInt();
|
||||||
|
int oy = oldSize.get(1).getAsInt();
|
||||||
|
int oz = oldSize.get(2).getAsInt();
|
||||||
|
if (newX < ox || newY < oy || newZ < oz) {
|
||||||
|
throw new IllegalArgumentException("Les nouvelles dimensions doivent etre >= aux anciennes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray paletteArr = root.getAsJsonArray("palette");
|
||||||
|
List<String> palette = new ArrayList<>(paletteArr.size());
|
||||||
|
for (JsonElement e : paletteArr) palette.add(e.getAsString());
|
||||||
|
if (palette.isEmpty() || !palette.get(0).equals(AIR)) {
|
||||||
|
// S'assurer que l'air est connu : si absent, l'ajouter en fin de palette.
|
||||||
|
int airIdx = palette.indexOf(AIR);
|
||||||
|
if (airIdx < 0) palette.add(AIR);
|
||||||
|
}
|
||||||
|
int airIdx = palette.indexOf(AIR);
|
||||||
|
if (airIdx < 0) airIdx = palette.size() - 1;
|
||||||
|
|
||||||
|
// Decoder l'ancien RLE en un tableau d'indices [oy*oz*ox].
|
||||||
|
int[] oldFlat = decodeBlocks(root.getAsJsonArray("blocks"), ox * oy * oz);
|
||||||
|
|
||||||
|
int totalNew = newX * newY * newZ;
|
||||||
|
int[] newFlat = new int[totalNew];
|
||||||
|
for (int i = 0; i < totalNew; i++) newFlat[i] = airIdx;
|
||||||
|
// Copier dans le nouveau plan (ordre Y -> Z -> X).
|
||||||
|
for (int y = 0; y < oy; y++) {
|
||||||
|
for (int z = 0; z < oz; z++) {
|
||||||
|
for (int x = 0; x < ox; x++) {
|
||||||
|
int oldIdx = y * (oz * ox) + z * ox + x;
|
||||||
|
int newIdx = y * (newZ * newX) + z * newX + x;
|
||||||
|
newFlat[newIdx] = oldFlat[oldIdx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionnellement marquer les 8 coins.
|
||||||
|
if (markCornerMaterial != null) {
|
||||||
|
String state = markCornerMaterial.createBlockData().getAsString();
|
||||||
|
int markIdx = palette.indexOf(state);
|
||||||
|
if (markIdx < 0) { palette.add(state); markIdx = palette.size() - 1; }
|
||||||
|
int[][] corners = {
|
||||||
|
{0, 0, 0},
|
||||||
|
{newX - 1, 0, 0},
|
||||||
|
{0, 0, newZ - 1},
|
||||||
|
{newX - 1, 0, newZ - 1},
|
||||||
|
{0, newY - 1, 0},
|
||||||
|
{newX - 1, newY - 1, 0},
|
||||||
|
{0, newY - 1, newZ - 1},
|
||||||
|
{newX - 1, newY - 1, newZ - 1}
|
||||||
|
};
|
||||||
|
for (int[] c : corners) {
|
||||||
|
int idx = c[1] * (newZ * newX) + c[2] * newX + c[0];
|
||||||
|
newFlat[idx] = markIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-encoder en RLE.
|
||||||
|
List<int[]> runs = new ArrayList<>();
|
||||||
|
int currentIdx = -1, run = 0;
|
||||||
|
for (int v : newFlat) {
|
||||||
|
if (v == currentIdx) { run++; }
|
||||||
|
else {
|
||||||
|
if (currentIdx != -1) runs.add(new int[]{currentIdx, run});
|
||||||
|
currentIdx = v; run = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (run > 0) runs.add(new int[]{currentIdx, run});
|
||||||
|
|
||||||
|
JsonObject out = new JsonObject();
|
||||||
|
JsonArray sizeArr = new JsonArray(); sizeArr.add(newX); sizeArr.add(newY); sizeArr.add(newZ);
|
||||||
|
out.add("size", sizeArr);
|
||||||
|
out.add("spawn", root.getAsJsonArray("spawn")); // spawn conserve
|
||||||
|
JsonArray pal = new JsonArray();
|
||||||
|
for (String p : palette) pal.add(p);
|
||||||
|
out.add("palette", pal);
|
||||||
|
JsonArray blocks = new JsonArray();
|
||||||
|
for (int[] r : runs) { JsonArray ja = new JsonArray(); ja.add(r[0]); ja.add(r[1]); blocks.add(ja); }
|
||||||
|
out.add("blocks", blocks);
|
||||||
|
// Conserve les containers (positions invariantes car ancres au coin 0,0,0).
|
||||||
|
if (root.has("containers")) out.add("containers", root.get("containers"));
|
||||||
|
return GSON.toJson(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decode le RLE en tableau d'indices pleins. */
|
||||||
|
private static int[] decodeBlocks(JsonArray blocksArr, int total) {
|
||||||
|
int[] out = new int[total];
|
||||||
|
int cursor = 0;
|
||||||
|
for (JsonElement el : blocksArr) {
|
||||||
|
JsonArray pair = el.getAsJsonArray();
|
||||||
|
int idx = pair.get(0).getAsInt();
|
||||||
|
int len = pair.get(1).getAsInt();
|
||||||
|
for (int j = 0; j < len && cursor < total; j++, cursor++) out[cursor] = idx;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ UTILITAIRES ============================ */
|
||||||
|
|
||||||
|
/** Vide la region (place de l'air partout). */
|
||||||
|
public static void clearRegion(World world,
|
||||||
|
int originX, int originY, int originZ,
|
||||||
|
int sizeX, int sizeY, int sizeZ) {
|
||||||
|
BlockData air = Bukkit.createBlockData(Material.AIR);
|
||||||
|
for (int y = 0; y < sizeY; y++) {
|
||||||
|
for (int z = 0; z < sizeZ; z++) {
|
||||||
|
for (int x = 0; x < sizeX; x++) {
|
||||||
|
Block b = world.getBlockAt(originX + x, originY + y, originZ + z);
|
||||||
|
if (b.getType() != Material.AIR) b.setBlockData(air, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True si la zone contient au moins un bloc non-air. */
|
||||||
|
public static boolean hasCollision(World world,
|
||||||
|
int originX, int originY, int originZ,
|
||||||
|
int sizeX, int sizeY, int sizeZ) {
|
||||||
|
for (int y = 0; y < sizeY; y++) {
|
||||||
|
for (int z = 0; z < sizeZ; z++) {
|
||||||
|
for (int x = 0; x < sizeX; x++) {
|
||||||
|
Block b = world.getBlockAt(originX + x, originY + y, originZ + z);
|
||||||
|
if (b.getType() != Material.AIR) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ DEFAULT SCHEMATIC ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genere une schematique par defaut : sol plein, mur d'un bloc avec piliers aux coins,
|
||||||
|
* et bordure de verre tout autour au niveau y=1. Le reste est de l'air.
|
||||||
|
* Le spawn par defaut est centre, sur le sol +1.
|
||||||
|
*/
|
||||||
|
public static String buildDefaultSchematic(int sizeX, int sizeY, int sizeZ,
|
||||||
|
Material platform, Material edge, Material glass) {
|
||||||
|
String platformState = platform.createBlockData().getAsString();
|
||||||
|
String edgeState = edge.createBlockData().getAsString();
|
||||||
|
String glassState = glass.createBlockData().getAsString();
|
||||||
|
|
||||||
|
Map<String, Integer> paletteIndex = new LinkedHashMap<>();
|
||||||
|
List<String> palette = new ArrayList<>();
|
||||||
|
// 0 = air toujours
|
||||||
|
paletteIndex.put(AIR, 0); palette.add(AIR);
|
||||||
|
|
||||||
|
int pIdx = paletteIndex.computeIfAbsent(platformState, s -> { palette.add(s); return palette.size() - 1; });
|
||||||
|
int eIdx = paletteIndex.computeIfAbsent(edgeState, s -> { palette.add(s); return palette.size() - 1; });
|
||||||
|
int gIdx = paletteIndex.computeIfAbsent(glassState, s -> { palette.add(s); return palette.size() - 1; });
|
||||||
|
|
||||||
|
List<int[]> blocks = new ArrayList<>();
|
||||||
|
int currentIdx = -1, run = 0;
|
||||||
|
|
||||||
|
for (int y = 0; y < sizeY; y++) {
|
||||||
|
for (int z = 0; z < sizeZ; z++) {
|
||||||
|
for (int x = 0; x < sizeX; x++) {
|
||||||
|
int idx;
|
||||||
|
boolean isEdgeXZ = (x == 0 || x == sizeX - 1) && (z == 0 || z == sizeZ - 1);
|
||||||
|
boolean onBorder = (x == 0 || x == sizeX - 1 || z == 0 || z == sizeZ - 1);
|
||||||
|
if (y == 0) {
|
||||||
|
idx = pIdx;
|
||||||
|
} else if (y == 1 && onBorder) {
|
||||||
|
idx = isEdgeXZ ? eIdx : gIdx;
|
||||||
|
} else if (y == sizeY - 1 && isEdgeXZ) {
|
||||||
|
idx = eIdx;
|
||||||
|
} else {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
if (idx == currentIdx) {
|
||||||
|
run++;
|
||||||
|
} else {
|
||||||
|
if (currentIdx != -1) blocks.add(new int[]{currentIdx, run});
|
||||||
|
currentIdx = idx;
|
||||||
|
run = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (run > 0) blocks.add(new int[]{currentIdx, run});
|
||||||
|
|
||||||
|
JsonObject root = new JsonObject();
|
||||||
|
JsonArray sizeArr = new JsonArray();
|
||||||
|
sizeArr.add(sizeX); sizeArr.add(sizeY); sizeArr.add(sizeZ);
|
||||||
|
root.add("size", sizeArr);
|
||||||
|
|
||||||
|
JsonArray spawnArr = new JsonArray();
|
||||||
|
spawnArr.add(sizeX / 2); spawnArr.add(1); spawnArr.add(sizeZ / 2);
|
||||||
|
root.add("spawn", spawnArr);
|
||||||
|
|
||||||
|
JsonArray paletteArr = new JsonArray();
|
||||||
|
for (String p : palette) paletteArr.add(p);
|
||||||
|
root.add("palette", paletteArr);
|
||||||
|
|
||||||
|
JsonArray blocksArr = new JsonArray();
|
||||||
|
for (int[] run2 : blocks) {
|
||||||
|
JsonArray ja = new JsonArray();
|
||||||
|
ja.add(run2[0]);
|
||||||
|
ja.add(run2[1]);
|
||||||
|
blocksArr.add(ja);
|
||||||
|
}
|
||||||
|
root.add("blocks", blocksArr);
|
||||||
|
|
||||||
|
return GSON.toJson(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la taille effective d'un spaceship en fonction de son niveau.
|
||||||
|
*
|
||||||
|
* Regle :
|
||||||
|
* - level 1 = taille de creation
|
||||||
|
* - chaque level-up alterne +1 sur x puis +1 sur z (x en premier)
|
||||||
|
* - tous les 4 level-ups : +1 sur y
|
||||||
|
* - les valeurs sont plafonnees a la taille maximale configuree.
|
||||||
|
*/
|
||||||
|
public final class ShipSizing {
|
||||||
|
|
||||||
|
private ShipSizing() {}
|
||||||
|
|
||||||
|
/** Renvoie [sizeX, sizeY, sizeZ] pour le niveau donne. */
|
||||||
|
public static int[] sizeForLevel(ConfigManager cfg, int level) {
|
||||||
|
if (level < 1) level = 1;
|
||||||
|
int additions = level - 1;
|
||||||
|
int xAdds = (additions + 1) / 2; // ceil(additions / 2)
|
||||||
|
int zAdds = additions / 2;
|
||||||
|
int yAdds = additions / 4;
|
||||||
|
|
||||||
|
int sx = clamp(cfg.defaultSizeX() + xAdds, cfg.defaultSizeX(), cfg.maxSizeX());
|
||||||
|
int sy = clamp(cfg.defaultSizeY() + yAdds, cfg.defaultSizeY(), cfg.maxSizeY());
|
||||||
|
int sz = clamp(cfg.defaultSizeZ() + zAdds, cfg.defaultSizeZ(), cfg.maxSizeZ());
|
||||||
|
return new int[]{sx, sy, sz};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Niveau a partir duquel toutes les dimensions atteignent leur max. */
|
||||||
|
public static int maxUsefulLevel(ConfigManager cfg) {
|
||||||
|
int xLevels = (cfg.maxSizeX() - cfg.defaultSizeX()) * 2; // x = ceil(adds/2)
|
||||||
|
int zLevels = (cfg.maxSizeZ() - cfg.defaultSizeZ()) * 2 + 1; // z = floor(adds/2)
|
||||||
|
int yLevels = (cfg.maxSizeY() - cfg.defaultSizeY()) * 4;
|
||||||
|
return 1 + Math.max(Math.max(xLevels, zLevels), yLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.inventory.ClickType;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI "Saut Spatial".
|
||||||
|
*
|
||||||
|
* Layout (36 slots) :
|
||||||
|
* Slot 4 : info (niveau, max, cout courant)
|
||||||
|
* Slots 11..16 : selection de direction (X-, X+, Z-, Z+, Y-, Y+)
|
||||||
|
* Slots 19..25 : ajustement de distance (-50 -10 -1 [val] +1 +10 +50)
|
||||||
|
* Slot 31 : confirmer
|
||||||
|
* Slot 35 : annuler
|
||||||
|
*/
|
||||||
|
public class SpaceJumpGUI implements SpaceshipGuiPanel {
|
||||||
|
|
||||||
|
private static final int SIZE = 36;
|
||||||
|
private static final int SLOT_INFO = 4;
|
||||||
|
private static final int SLOT_X_MINUS = 11;
|
||||||
|
private static final int SLOT_X_PLUS = 12;
|
||||||
|
private static final int SLOT_Z_MINUS = 13;
|
||||||
|
private static final int SLOT_Z_PLUS = 14;
|
||||||
|
private static final int SLOT_Y_MINUS = 15;
|
||||||
|
private static final int SLOT_Y_PLUS = 16;
|
||||||
|
private static final int SLOT_M50 = 19, SLOT_M10 = 20, SLOT_M1 = 21;
|
||||||
|
private static final int SLOT_DIST = 22;
|
||||||
|
private static final int SLOT_P1 = 23, SLOT_P10 = 24, SLOT_P50 = 25;
|
||||||
|
private static final int SLOT_CONFIRM = 31;
|
||||||
|
private static final int SLOT_CANCEL = 35;
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final Player player;
|
||||||
|
private final Spaceship ship;
|
||||||
|
private final SpaceshipGUIHolder holder;
|
||||||
|
private Inventory inv;
|
||||||
|
|
||||||
|
private char axis = 'X';
|
||||||
|
private int signedDistance = 10;
|
||||||
|
|
||||||
|
public SpaceJumpGUI(SpaceShipProject plugin, Player player, Spaceship ship) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.player = player;
|
||||||
|
this.ship = ship;
|
||||||
|
this.holder = new SpaceshipGUIHolder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
String title = ConfigManager.color("&8\u27FC &bSaut Spatial &8\u2014 &f" + ship.getName());
|
||||||
|
inv = Bukkit.createInventory(holder, SIZE, title);
|
||||||
|
holder.setInventory(inv);
|
||||||
|
render();
|
||||||
|
SpaceshipGUIListener.registerOpenJump(player, this);
|
||||||
|
player.openInventory(inv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spaceship getShip() { return ship; }
|
||||||
|
|
||||||
|
private void render() {
|
||||||
|
for (int i = 0; i < SIZE; i++) inv.setItem(i, filler());
|
||||||
|
|
||||||
|
int max = plugin.getSpaceshipManager().spaceJumpMax(ship.getLevel());
|
||||||
|
if (Math.abs(signedDistance) > max) {
|
||||||
|
signedDistance = (signedDistance < 0 ? -1 : 1) * max;
|
||||||
|
}
|
||||||
|
if (Math.abs(signedDistance) < 1) signedDistance = 1;
|
||||||
|
|
||||||
|
double cost = plugin.getSpaceshipManager().spaceJumpCost(Math.abs(signedDistance));
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
String balance = eco.isReady() ? eco.format(eco.getBalance(player.getUniqueId())) : "-";
|
||||||
|
String costStr = eco.isReady() ? eco.format(cost) : String.valueOf(cost);
|
||||||
|
|
||||||
|
inv.setItem(SLOT_INFO, item(Material.ENDER_EYE,
|
||||||
|
"&b&l\u2730 Saut Spatial",
|
||||||
|
List.of(
|
||||||
|
"&7Spaceship : &f" + ship.getName(),
|
||||||
|
"&7Niveau : &e" + ship.getLevel() + " &7(max &f" + max + " &7blocs)",
|
||||||
|
"&7Direction : &b" + dirLabel(),
|
||||||
|
"&7Distance : &f" + Math.abs(signedDistance),
|
||||||
|
"&7Cout : &6" + costStr,
|
||||||
|
"&7Solde : &6" + balance
|
||||||
|
)));
|
||||||
|
|
||||||
|
inv.setItem(SLOT_X_MINUS, dirButton('X', -1, "&aX-", "Ouest"));
|
||||||
|
inv.setItem(SLOT_X_PLUS, dirButton('X', 1, "&aX+", "Est"));
|
||||||
|
inv.setItem(SLOT_Z_MINUS, dirButton('Z', -1, "&aZ-", "Nord"));
|
||||||
|
inv.setItem(SLOT_Z_PLUS, dirButton('Z', 1, "&aZ+", "Sud"));
|
||||||
|
inv.setItem(SLOT_Y_MINUS, dirButton('Y', -1, "&aY-", "Bas"));
|
||||||
|
inv.setItem(SLOT_Y_PLUS, dirButton('Y', 1, "&aY+", "Haut"));
|
||||||
|
|
||||||
|
inv.setItem(SLOT_M50, item(Material.RED_CONCRETE, "&c-50", List.of("&7Reduit la distance.")));
|
||||||
|
inv.setItem(SLOT_M10, item(Material.RED_CONCRETE, "&c-10", List.of("&7Reduit la distance.")));
|
||||||
|
inv.setItem(SLOT_M1, item(Material.RED_CONCRETE, "&c-1", List.of("&7Reduit la distance.")));
|
||||||
|
inv.setItem(SLOT_DIST, item(Material.PAPER,
|
||||||
|
"&f&lDistance : " + Math.abs(signedDistance),
|
||||||
|
List.of("&7Cout : &6" + costStr,
|
||||||
|
"&7Max : &f" + max,
|
||||||
|
"&7Direction : &b" + dirLabel())));
|
||||||
|
inv.setItem(SLOT_P1, item(Material.LIME_CONCRETE, "&a+1", List.of("&7Augmente la distance.")));
|
||||||
|
inv.setItem(SLOT_P10, item(Material.LIME_CONCRETE, "&a+10", List.of("&7Augmente la distance.")));
|
||||||
|
inv.setItem(SLOT_P50, item(Material.LIME_CONCRETE, "&a+50", List.of("&7Augmente la distance.")));
|
||||||
|
|
||||||
|
boolean canAfford = !eco.isReady() || eco.has(player.getUniqueId(), cost);
|
||||||
|
inv.setItem(SLOT_CONFIRM, item(
|
||||||
|
canAfford ? Material.EMERALD_BLOCK : Material.REDSTONE_BLOCK,
|
||||||
|
canAfford ? "&a&l\u2714 CONFIRMER" : "&c\u2718 Fonds insuffisants",
|
||||||
|
List.of(
|
||||||
|
"&7Direction : &b" + dirLabel(),
|
||||||
|
"&7Distance : &f" + Math.abs(signedDistance),
|
||||||
|
"&7Cout : &6" + costStr,
|
||||||
|
"",
|
||||||
|
canAfford ? "&aClic pour lancer le saut." : "&cVotre solde est trop bas."
|
||||||
|
)
|
||||||
|
));
|
||||||
|
inv.setItem(SLOT_CANCEL, item(Material.BARRIER, "&c\u2718 Annuler", List.of("&7Ferme la fenetre.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dirLabel() {
|
||||||
|
return switch (axis) {
|
||||||
|
case 'X' -> signedDistance >= 0 ? "Est (X+)" : "Ouest (X-)";
|
||||||
|
case 'Y' -> signedDistance >= 0 ? "Haut (Y+)" : "Bas (Y-)";
|
||||||
|
case 'Z' -> signedDistance >= 0 ? "Sud (Z+)" : "Nord (Z-)";
|
||||||
|
default -> "?";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack dirButton(char a, int sign, String name, String label) {
|
||||||
|
boolean selected = axis == a && Integer.signum(signedDistance) == sign;
|
||||||
|
return item(selected ? Material.LIME_STAINED_GLASS_PANE : Material.GRAY_STAINED_GLASS_PANE,
|
||||||
|
(selected ? "&a&l" : "&7") + name + " &7(" + label + ")",
|
||||||
|
List.of(selected ? "&aSelectionne" : "&7Clic pour selectionner"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack filler() {
|
||||||
|
ItemStack stack = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
|
||||||
|
ItemMeta meta = stack.getItemMeta();
|
||||||
|
if (meta != null) { meta.setDisplayName(" "); stack.setItemMeta(meta); }
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack item(Material mat, String name, List<String> lore) {
|
||||||
|
ItemStack stack = new ItemStack(mat);
|
||||||
|
ItemMeta meta = stack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ConfigManager.color(name));
|
||||||
|
List<String> colored = new ArrayList<>(lore.size());
|
||||||
|
for (String s : lore) colored.add(ConfigManager.color(s));
|
||||||
|
meta.setLore(colored);
|
||||||
|
stack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleClick(InventoryClickEvent event) {
|
||||||
|
int slot = event.getRawSlot();
|
||||||
|
switch (slot) {
|
||||||
|
case SLOT_X_MINUS -> { axis = 'X'; signedDistance = -Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_X_PLUS -> { axis = 'X'; signedDistance = Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_Z_MINUS -> { axis = 'Z'; signedDistance = -Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_Z_PLUS -> { axis = 'Z'; signedDistance = Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_Y_MINUS -> { axis = 'Y'; signedDistance = -Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_Y_PLUS -> { axis = 'Y'; signedDistance = Math.abs(currentDist()); render(); }
|
||||||
|
case SLOT_M50 -> { adjust(-50, event.getClick()); }
|
||||||
|
case SLOT_M10 -> { adjust(-10, event.getClick()); }
|
||||||
|
case SLOT_M1 -> { adjust(-1, event.getClick()); }
|
||||||
|
case SLOT_P1 -> { adjust( 1, event.getClick()); }
|
||||||
|
case SLOT_P10 -> { adjust( 10, event.getClick()); }
|
||||||
|
case SLOT_P50 -> { adjust( 50, event.getClick()); }
|
||||||
|
case SLOT_CONFIRM -> {
|
||||||
|
int sign = Integer.signum(signedDistance);
|
||||||
|
if (sign == 0) sign = 1;
|
||||||
|
int abs = Math.abs(signedDistance);
|
||||||
|
player.closeInventory();
|
||||||
|
plugin.getSpaceshipManager().spaceJump(player, ship, axis, sign * abs);
|
||||||
|
}
|
||||||
|
case SLOT_CANCEL -> player.closeInventory();
|
||||||
|
default -> { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int currentDist() { int d = Math.abs(signedDistance); return d == 0 ? 1 : d; }
|
||||||
|
|
||||||
|
private void adjust(int delta, ClickType click) {
|
||||||
|
// Shift = x10 pour aller plus vite.
|
||||||
|
if (click == ClickType.SHIFT_LEFT || click == ClickType.SHIFT_RIGHT) delta *= 10;
|
||||||
|
int sign = Integer.signum(signedDistance);
|
||||||
|
if (sign == 0) sign = 1;
|
||||||
|
int abs = Math.abs(signedDistance) + delta;
|
||||||
|
if (abs < 1) abs = 1;
|
||||||
|
int max = plugin.getSpaceshipManager().spaceJumpMax(ship.getLevel());
|
||||||
|
if (abs > max) abs = max;
|
||||||
|
signedDistance = sign * abs;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commande admin /spaceshipadmin (/ssa).
|
||||||
|
*
|
||||||
|
* Sous-commandes :
|
||||||
|
* - reload : recharge config + modeles
|
||||||
|
* - list [joueur] : liste les ships (tous ou d'un joueur)
|
||||||
|
* - delete <id> : supprime un ship n'importe lequel
|
||||||
|
* - addmoney <joueur> <id> <montant> : donne de la money a un ship
|
||||||
|
* - setlevel <joueur> <id> <level> : force le niveau d'un ship
|
||||||
|
* - setname <joueur> <id> <nom...> : renomme un ship
|
||||||
|
* - setconfig <chemin> <valeur> : modifie config.yml
|
||||||
|
* - getconfig <chemin> : lit config.yml
|
||||||
|
* - loadmodel [nom] : place le modele dans le monde pour edition
|
||||||
|
* - savemodel : sauvegarde la session active
|
||||||
|
* - cancelmodel : annule la session active
|
||||||
|
*/
|
||||||
|
public class SpaceShipAdminCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
|
||||||
|
public SpaceShipAdminCommand(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!sender.hasPermission("spaceship.admin")) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
|
||||||
|
help(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "reload" -> handleReload(sender);
|
||||||
|
case "list" -> handleList(sender, args);
|
||||||
|
case "delete" -> handleDelete(sender, args);
|
||||||
|
case "addmoney" -> handleAddMoney(sender, args);
|
||||||
|
case "setlevel" -> handleSetLevel(sender, args);
|
||||||
|
case "setname" -> handleSetName(sender, args);
|
||||||
|
case "setconfig" -> handleSetConfig(sender, args);
|
||||||
|
case "getconfig" -> handleGetConfig(sender, args);
|
||||||
|
case "loadmodel" -> handleLoadModel(sender, args);
|
||||||
|
case "savemodel" -> handleSaveModel(sender);
|
||||||
|
case "cancelmodel" -> handleCancelModel(sender);
|
||||||
|
default -> sender.sendMessage(ConfigManager.color("&cSous-commande inconnue. /ssa help"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void help(CommandSender s) {
|
||||||
|
s.sendMessage(ConfigManager.color("&c&l=== SpaceShipAdmin ==="));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa reload"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa list [joueur]"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa delete <id>"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa addmoney <joueur> <montant> &8(via Vault)"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa setlevel <joueur> <id> <level>"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa setname <joueur> <id> <nom...>"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa setconfig <chemin> <valeur>"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa getconfig <chemin>"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa loadmodel [nom]"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa savemodel"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/ssa cancelmodel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ RELOAD ============================ */
|
||||||
|
|
||||||
|
private void handleReload(CommandSender s) {
|
||||||
|
plugin.getConfigManager().reload();
|
||||||
|
plugin.getModelManager().reload();
|
||||||
|
s.sendMessage(ConfigManager.color("&aConfig et modeles recharges."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ LIST ============================ */
|
||||||
|
|
||||||
|
private void handleList(CommandSender s, String[] args) {
|
||||||
|
if (args.length >= 2) {
|
||||||
|
OfflinePlayer p = resolveOfflinePlayer(args[1]);
|
||||||
|
if (p == null) { s.sendMessage(ConfigManager.color("&cJoueur introuvable.")); return; }
|
||||||
|
List<Spaceship> ships = plugin.getDatabaseManager().getSpaceshipsByOwner(p.getUniqueId());
|
||||||
|
s.sendMessage(ConfigManager.color("&bShips de &f" + (p.getName() != null ? p.getName() : args[1])
|
||||||
|
+ " &b: &f" + ships.size()));
|
||||||
|
for (Spaceship ship : ships) {
|
||||||
|
s.sendMessage(ConfigManager.color("&7- &f#" + ship.getId() + " &b" + ship.getName()
|
||||||
|
+ " &7Lv&f" + ship.getLevel() + " &7Taille&f " + ship.getSizeX() + "x" + ship.getSizeY() + "x" + ship.getSizeZ()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Liste de tous les loaded actuels
|
||||||
|
List<LoadedSpaceship> loaded = plugin.getDatabaseManager().getAllLoaded();
|
||||||
|
s.sendMessage(ConfigManager.color("&bSpaceships actuellement charges : &f" + loaded.size()));
|
||||||
|
for (LoadedSpaceship l : loaded) {
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(l.getSpaceshipId());
|
||||||
|
if (ship == null) continue;
|
||||||
|
s.sendMessage(ConfigManager.color("&7- &f#" + ship.getId() + " &b" + ship.getName()
|
||||||
|
+ " &7@ " + l.getWorldName() + " " + l.getOriginX() + "," + l.getOriginY() + "," + l.getOriginZ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ DELETE ============================ */
|
||||||
|
|
||||||
|
private void handleDelete(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 2) { s.sendMessage(ConfigManager.color("&cUsage : /ssa delete <id>")); return; }
|
||||||
|
long id;
|
||||||
|
try { id = Long.parseLong(args[1]); } catch (NumberFormatException e) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cID invalide.")); return;
|
||||||
|
}
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(id);
|
||||||
|
if (ship == null) { s.sendMessage(ConfigManager.color("&cShip introuvable.")); return; }
|
||||||
|
plugin.getSpaceshipManager().delete(ship);
|
||||||
|
s.sendMessage(ConfigManager.color("&aShip #" + id + " supprime."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ MONEY / LEVEL / NAME ============================ */
|
||||||
|
|
||||||
|
private void handleAddMoney(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 3) { s.sendMessage(ConfigManager.color("&cUsage : /ssa addmoney <joueur> <montant>")); return; }
|
||||||
|
OfflinePlayer p = resolveOfflinePlayer(args[1]);
|
||||||
|
if (p == null) { s.sendMessage(ConfigManager.color("&cJoueur introuvable.")); return; }
|
||||||
|
double amount;
|
||||||
|
try { amount = Double.parseDouble(args[2]); } catch (NumberFormatException e) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cMontant invalide.")); return;
|
||||||
|
}
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
if (!eco.isReady()) { s.sendMessage(plugin.getConfigManager().msgVaultUnavailable()); return; }
|
||||||
|
if (!eco.deposit(p.getUniqueId(), amount)) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cEchec du depot."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(ConfigManager.color("&a+" + eco.format(amount) + " depose sur le solde Vault de "
|
||||||
|
+ (p.getName() != null ? p.getName() : args[1]) + "."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetLevel(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 4) { s.sendMessage(ConfigManager.color("&cUsage : /ssa setlevel <joueur> <id> <level>")); return; }
|
||||||
|
Spaceship ship = resolveShip(s, args[1], args[2]);
|
||||||
|
if (ship == null) return;
|
||||||
|
int level;
|
||||||
|
try { level = Math.max(1, Integer.parseInt(args[3])); } catch (NumberFormatException e) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cNiveau invalide.")); return;
|
||||||
|
}
|
||||||
|
ship.setLevel(level);
|
||||||
|
plugin.getDatabaseManager().updateSpaceship(ship);
|
||||||
|
s.sendMessage(ConfigManager.color("&aNiveau de #" + ship.getId() + " : &e" + level));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetName(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 4) { s.sendMessage(ConfigManager.color("&cUsage : /ssa setname <joueur> <id> <nom...>")); return; }
|
||||||
|
Spaceship ship = resolveShip(s, args[1], args[2]);
|
||||||
|
if (ship == null) return;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 3; i < args.length; i++) {
|
||||||
|
if (i > 3) sb.append(' ');
|
||||||
|
sb.append(args[i]);
|
||||||
|
}
|
||||||
|
String name = sb.toString();
|
||||||
|
if (name.length() > 32) name = name.substring(0, 32);
|
||||||
|
ship.setName(name);
|
||||||
|
plugin.getDatabaseManager().updateSpaceship(ship);
|
||||||
|
s.sendMessage(ConfigManager.color("&aShip renomme : &b" + name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spaceship resolveShip(CommandSender s, String playerName, String idStr) {
|
||||||
|
OfflinePlayer p = resolveOfflinePlayer(playerName);
|
||||||
|
if (p == null) { s.sendMessage(ConfigManager.color("&cJoueur introuvable.")); return null; }
|
||||||
|
long id;
|
||||||
|
try { id = Long.parseLong(idStr); } catch (NumberFormatException e) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cID invalide.")); return null;
|
||||||
|
}
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(id);
|
||||||
|
if (ship == null) { s.sendMessage(ConfigManager.color("&cShip introuvable.")); return null; }
|
||||||
|
if (!ship.getOwnerUuid().equals(p.getUniqueId())) {
|
||||||
|
s.sendMessage(ConfigManager.color("&cLe ship #" + id + " n'appartient pas a " + playerName + ".")); return null;
|
||||||
|
}
|
||||||
|
return ship;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ CONFIG ============================ */
|
||||||
|
|
||||||
|
private void handleSetConfig(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 3) { s.sendMessage(ConfigManager.color("&cUsage : /ssa setconfig <chemin> <valeur>")); return; }
|
||||||
|
String path = args[1];
|
||||||
|
StringBuilder valueB = new StringBuilder();
|
||||||
|
for (int i = 2; i < args.length; i++) { if (i > 2) valueB.append(' '); valueB.append(args[i]); }
|
||||||
|
Object parsed = parseValue(valueB.toString());
|
||||||
|
FileConfiguration cfg = plugin.getConfig();
|
||||||
|
cfg.set(path, parsed);
|
||||||
|
plugin.saveConfig();
|
||||||
|
plugin.getConfigManager().reload();
|
||||||
|
s.sendMessage(ConfigManager.color("&aConfig &f" + path + " &asetee a &f" + parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGetConfig(CommandSender s, String[] args) {
|
||||||
|
if (args.length < 2) { s.sendMessage(ConfigManager.color("&cUsage : /ssa getconfig <chemin>")); return; }
|
||||||
|
Object v = plugin.getConfig().get(args[1]);
|
||||||
|
s.sendMessage(ConfigManager.color("&b" + args[1] + " &7= &f" + v));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object parseValue(String s) {
|
||||||
|
if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) return Boolean.parseBoolean(s);
|
||||||
|
try { return Integer.parseInt(s); } catch (NumberFormatException ignored) {}
|
||||||
|
try { return Double.parseDouble(s); } catch (NumberFormatException ignored) {}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ MODEL ============================ */
|
||||||
|
|
||||||
|
private void handleLoadModel(CommandSender s, String[] args) {
|
||||||
|
if (!(s instanceof Player player)) { s.sendMessage(ConfigManager.color("&cCommande joueur uniquement.")); return; }
|
||||||
|
String modelName = args.length >= 2 ? args[1] : "default";
|
||||||
|
plugin.getModelManager().loadForEdit(player, modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSaveModel(CommandSender s) {
|
||||||
|
if (!(s instanceof Player player)) { s.sendMessage(ConfigManager.color("&cCommande joueur uniquement.")); return; }
|
||||||
|
plugin.getModelManager().saveEdit(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCancelModel(CommandSender s) {
|
||||||
|
if (!(s instanceof Player player)) { s.sendMessage(ConfigManager.color("&cCommande joueur uniquement.")); return; }
|
||||||
|
plugin.getModelManager().cancelEdit(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ HELPERS ============================ */
|
||||||
|
|
||||||
|
private OfflinePlayer resolveOfflinePlayer(String name) {
|
||||||
|
Player p = plugin.getServer().getPlayer(name);
|
||||||
|
if (p != null) return p;
|
||||||
|
return plugin.getServer().getOfflinePlayerIfCached(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||||
|
if (!sender.hasPermission("spaceship.admin")) return new ArrayList<>();
|
||||||
|
if (args.length == 1) {
|
||||||
|
return Arrays.asList("help", "reload", "list", "delete", "addmoney", "setlevel",
|
||||||
|
"setname", "setconfig", "getconfig", "loadmodel", "savemodel", "cancelmodel");
|
||||||
|
}
|
||||||
|
if (args.length == 2 && (args[0].equalsIgnoreCase("list")
|
||||||
|
|| args[0].equalsIgnoreCase("addmoney")
|
||||||
|
|| args[0].equalsIgnoreCase("setlevel")
|
||||||
|
|| args[0].equalsIgnoreCase("setname"))) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Player p : plugin.getServer().getOnlinePlayers()) out.add(p.getName());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (args.length == 3 && (args[0].equalsIgnoreCase("setlevel")
|
||||||
|
|| args[0].equalsIgnoreCase("setname"))) {
|
||||||
|
Player target = plugin.getServer().getPlayer(args[1]);
|
||||||
|
if (target != null) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Spaceship s : plugin.getDatabaseManager().getSpaceshipsByOwner(target.getUniqueId())) {
|
||||||
|
out.add(String.valueOf(s.getId()));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (args.length == 2 && args[0].equalsIgnoreCase("loadmodel")) {
|
||||||
|
return Arrays.asList("default");
|
||||||
|
}
|
||||||
|
if (args.length == 2 && args[0].equalsIgnoreCase("setconfig")) {
|
||||||
|
return Arrays.asList("max-ships-per-player", "default-size.x", "default-size.y", "default-size.z",
|
||||||
|
"summon-offset-y", "safety.check-collision", "safety.min-y-above-player");
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commandes joueur de SpaceShipProject :
|
||||||
|
* - /spaceship help
|
||||||
|
* - /spaceship create : cree un nouveau spaceship (boussole donnee)
|
||||||
|
* - /spaceship give <player> <id> : recupere une telecommande perdue (proprietaire requis)
|
||||||
|
* - /spaceship list [joueur] : liste les ships
|
||||||
|
* - /spaceship info [joueur] : infos detaillees
|
||||||
|
* - /spaceship delete <id> : supprime un de ses ships (boussole retiree)
|
||||||
|
* - /spaceship remote <id> : alias de give pour soi-meme
|
||||||
|
* - /spaceship reload : recharge la config
|
||||||
|
*/
|
||||||
|
public class SpaceShipCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
|
||||||
|
public SpaceShipCommand(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
|
||||||
|
help(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "create" -> handleCreate(sender);
|
||||||
|
case "give" -> handleGive(sender, args);
|
||||||
|
case "remote" -> handleRemoteSelf(sender, args);
|
||||||
|
case "list" -> handleList(sender, args);
|
||||||
|
case "info" -> handleInfo(sender, args);
|
||||||
|
case "delete" -> handleDelete(sender, args);
|
||||||
|
case "reload" -> handleReload(sender);
|
||||||
|
default -> sender.sendMessage(ConfigManager.color("&cCommande inconnue. /spaceship help"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void help(CommandSender s) {
|
||||||
|
s.sendMessage(ConfigManager.color("&b&l=== SpaceShipProject ==="));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship create &8- creer un nouveau spaceship"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship give <joueur> <id> &8- recuperer une telecommande perdue"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship remote <id> &8- recuperer sa propre telecommande"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship list [joueur] &8- lister les spaceships"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship info [joueur] &8- infos detaillees"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship delete <id> &8- supprimer un spaceship"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7/spaceship reload &8- recharger la config"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7Telecommande : "));
|
||||||
|
s.sendMessage(ConfigManager.color("&7 &eClic gauche &8: invoquer / decharger"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7 &eClic droit &8: monter / redescendre"));
|
||||||
|
s.sendMessage(ConfigManager.color("&7 &eShift + droit &8: parametres"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ CREATE ============================ */
|
||||||
|
|
||||||
|
private void handleCreate(CommandSender sender) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cCommande joueur uniquement."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!player.hasPermission("spaceship.create")) {
|
||||||
|
player.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long id = plugin.getSpaceshipManager().createNewSpaceship(player);
|
||||||
|
if (id < 0) return; // message deja envoye (quota)
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(id);
|
||||||
|
if (ship == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cErreur interne : ship introuvable apres creation."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
giveRemote(player, ship);
|
||||||
|
player.sendMessage(plugin.getConfigManager().msgGiven(ship.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ GIVE (recover) ============================ */
|
||||||
|
|
||||||
|
private void handleGive(CommandSender sender, String[] args) {
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cUsage : /spaceship give <joueur> <id>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!sender.hasPermission("spaceship.give")) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player target = plugin.getServer().getPlayer(args[1]);
|
||||||
|
if (target == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cJoueur introuvable (doit etre en ligne)."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long id;
|
||||||
|
try { id = Long.parseLong(args[2]); }
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cID invalide."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(id);
|
||||||
|
if (ship == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cSpaceship introuvable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ship.getOwnerUuid().equals(target.getUniqueId())
|
||||||
|
&& !sender.hasPermission("spaceship.admin")) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cLe spaceship #" + id + " n'appartient pas a " + target.getName() + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nettoie d'eventuels doublons et compte ce qui reste.
|
||||||
|
plugin.getSpaceshipManager().sanitizePlayerRemotes(target);
|
||||||
|
int existing = countRemotes(target, id);
|
||||||
|
if (existing >= 1) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&7" + target.getName()
|
||||||
|
+ " possede deja la telecommande #" + id + " (rien a faire)."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
giveRemote(target, ship);
|
||||||
|
sender.sendMessage(ConfigManager.color("&aTelecommande #" + id + " donnee a " + target.getName() + "."));
|
||||||
|
if (target != sender) {
|
||||||
|
target.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&aTelecommande &b" + ship.getName() + " &arendue."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Version courte pour soi-meme : /spaceship remote <id>. */
|
||||||
|
private void handleRemoteSelf(CommandSender sender, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cCommande joueur uniquement."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cUsage : /spaceship remote <id>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reutilise la logique de give pour soi-meme.
|
||||||
|
handleGive(sender, new String[]{"give", player.getName(), args[1]});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countRemotes(Player p, long id) {
|
||||||
|
int n = 0;
|
||||||
|
for (ItemStack it : p.getInventory().getContents()) {
|
||||||
|
if (it == null || !plugin.getRemoteManager().isRemote(it)) continue;
|
||||||
|
long sid = plugin.getRemoteManager().getShipId(it);
|
||||||
|
if (sid == id) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void giveRemote(Player target, Spaceship ship) {
|
||||||
|
ItemStack remote = plugin.getRemoteManager().buildRemote(ship);
|
||||||
|
HashMap<Integer, ItemStack> remaining = target.getInventory().addItem(remote);
|
||||||
|
if (!remaining.isEmpty()) {
|
||||||
|
target.getWorld().dropItemNaturally(target.getLocation(), remote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ LIST ============================ */
|
||||||
|
|
||||||
|
private void handleList(CommandSender sender, String[] args) {
|
||||||
|
UUID target; String name;
|
||||||
|
if (args.length >= 2) {
|
||||||
|
if (!sender.hasPermission("spaceship.admin")) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OfflinePlayer p = resolveOfflinePlayer(args[1]);
|
||||||
|
if (p == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cJoueur introuvable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = p.getUniqueId();
|
||||||
|
name = p.getName() != null ? p.getName() : args[1];
|
||||||
|
} else {
|
||||||
|
if (!(sender instanceof Player p)) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cSpecifiez un joueur."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = p.getUniqueId();
|
||||||
|
name = p.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Spaceship> ships = plugin.getDatabaseManager().getSpaceshipsByOwner(target);
|
||||||
|
sender.sendMessage(ConfigManager.color("&bSpaceships de &f" + name + " &b: &f" + ships.size()));
|
||||||
|
for (Spaceship s : ships) {
|
||||||
|
boolean loaded = plugin.getSpaceshipManager().isLoaded(s.getId());
|
||||||
|
sender.sendMessage(ConfigManager.color("&7- &f#" + s.getId() + " &b" + s.getName()
|
||||||
|
+ " &7Lv&f" + s.getLevel() + " &7" + s.getSizeX() + "x" + s.getSizeY() + "x" + s.getSizeZ()
|
||||||
|
+ " &7" + (loaded ? "&aCHARGE" : "&8decharge")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ INFO ============================ */
|
||||||
|
|
||||||
|
private void handleInfo(CommandSender sender, String[] args) {
|
||||||
|
UUID target; String name;
|
||||||
|
if (args.length >= 2) {
|
||||||
|
if (!sender.hasPermission("spaceship.info.other") && !sender.hasPermission("spaceship.admin")) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OfflinePlayer p = resolveOfflinePlayer(args[1]);
|
||||||
|
if (p == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cJoueur introuvable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = p.getUniqueId();
|
||||||
|
name = p.getName() != null ? p.getName() : args[1];
|
||||||
|
} else {
|
||||||
|
if (!(sender instanceof Player p)) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cSpecifiez un joueur."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = p.getUniqueId();
|
||||||
|
name = p.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Spaceship> ships = plugin.getDatabaseManager().getSpaceshipsByOwner(target);
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
double balance = eco.isReady() ? eco.getBalance(target) : 0;
|
||||||
|
sender.sendMessage(ConfigManager.color("&b&l== Infos spaceships de &f" + name + " &b&l(" + ships.size() + ") =="));
|
||||||
|
sender.sendMessage(ConfigManager.color("&7Solde Vault : &6" + (eco.isReady() ? eco.format(balance) : "-")));
|
||||||
|
for (Spaceship s : ships) {
|
||||||
|
LoadedSpaceship loaded = plugin.getSpaceshipManager().getLoaded(s.getId());
|
||||||
|
String status = loaded != null
|
||||||
|
? "&aCHARGE &7@ " + loaded.getWorldName() + " " + loaded.getOriginX() + "," + loaded.getOriginY() + "," + loaded.getOriginZ()
|
||||||
|
: "&8decharge";
|
||||||
|
double cost = plugin.getSpaceshipManager().upgradeCost(s.getLevel());
|
||||||
|
int[] nextSize = ShipSizing.sizeForLevel(plugin.getConfigManager(), s.getLevel() + 1);
|
||||||
|
int jumpMax = plugin.getSpaceshipManager().spaceJumpMax(s.getLevel());
|
||||||
|
sender.sendMessage(ConfigManager.color("&7-------------------------"));
|
||||||
|
sender.sendMessage(ConfigManager.color("&b#" + s.getId() + " &f" + s.getName()));
|
||||||
|
sender.sendMessage(ConfigManager.color(" &7Statut : " + status));
|
||||||
|
sender.sendMessage(ConfigManager.color(" &7Niveau : &e" + s.getLevel()
|
||||||
|
+ " &7(upgrade : &6" + (eco.isReady() ? eco.format(cost) : cost) + "&7)"));
|
||||||
|
sender.sendMessage(ConfigManager.color(" &7Taille : &f" + s.getSizeX() + "x" + s.getSizeY() + "x" + s.getSizeZ()
|
||||||
|
+ " &7(prochaine : &f" + nextSize[0] + "x" + nextSize[1] + "x" + nextSize[2] + "&7)"));
|
||||||
|
sender.sendMessage(ConfigManager.color(" &7Saut max : &f" + jumpMax + " &7blocs"));
|
||||||
|
sender.sendMessage(ConfigManager.color(" &7Spawn (rel.) : &f" + s.getSpawnX() + "," + s.getSpawnY() + "," + s.getSpawnZ()));
|
||||||
|
}
|
||||||
|
if (ships.isEmpty()) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&7Aucun spaceship."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ DELETE ============================ */
|
||||||
|
|
||||||
|
private void handleDelete(CommandSender sender, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cUsage : /spaceship delete <id>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long id;
|
||||||
|
try { id = Long.parseLong(args[1]); }
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cID invalide."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(id);
|
||||||
|
if (ship == null) {
|
||||||
|
sender.sendMessage(ConfigManager.color("&cSpaceship introuvable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!sender.hasPermission("spaceship.admin")
|
||||||
|
&& (!(sender instanceof Player p) || !ship.getOwnerUuid().equals(p.getUniqueId()))) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNotOwner());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getSpaceshipManager().delete(ship);
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgDeleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ RELOAD ============================ */
|
||||||
|
|
||||||
|
private void handleReload(CommandSender sender) {
|
||||||
|
if (!sender.hasPermission("spaceship.reload")) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().msgNoPerm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getConfigManager().reload();
|
||||||
|
plugin.getModelManager().reload();
|
||||||
|
sender.sendMessage(ConfigManager.color("&aConfiguration rechargee."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ HELPERS ============================ */
|
||||||
|
|
||||||
|
private OfflinePlayer resolveOfflinePlayer(String name) {
|
||||||
|
Player p = plugin.getServer().getPlayer(name);
|
||||||
|
if (p != null) return p;
|
||||||
|
OfflinePlayer off = plugin.getServer().getOfflinePlayerIfCached(name);
|
||||||
|
return off; // peut etre null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
return Arrays.asList("help", "create", "give", "remote", "list", "info", "delete", "reload");
|
||||||
|
}
|
||||||
|
if (args.length == 2 && (args[0].equalsIgnoreCase("give")
|
||||||
|
|| args[0].equalsIgnoreCase("list")
|
||||||
|
|| args[0].equalsIgnoreCase("info"))) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Player p : plugin.getServer().getOnlinePlayers()) out.add(p.getName());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (args.length == 2 && (args[0].equalsIgnoreCase("delete") || args[0].equalsIgnoreCase("remote"))
|
||||||
|
&& sender instanceof Player p) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Spaceship s : plugin.getDatabaseManager().getSpaceshipsByOwner(p.getUniqueId())) {
|
||||||
|
out.add(String.valueOf(s.getId()));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (args.length == 3 && args[0].equalsIgnoreCase("give")) {
|
||||||
|
Player target = plugin.getServer().getPlayer(args[1]);
|
||||||
|
if (target != null) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for (Spaceship s : plugin.getDatabaseManager().getSpaceshipsByOwner(target.getUniqueId())) {
|
||||||
|
out.add(String.valueOf(s.getId()));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe principale du plugin SpaceShipProject.
|
||||||
|
*
|
||||||
|
* Concept :
|
||||||
|
* - Chaque joueur peut posseder plusieurs spaceships personnels.
|
||||||
|
* - Chaque spaceship a sa propre telecommande (boussole).
|
||||||
|
* - Le spaceship est charge dans la meme map que le joueur, en plein ciel.
|
||||||
|
* - Seul le proprietaire peut le modifier.
|
||||||
|
* - Toutes les modifications sont sauvegardees en base au moment du dechargement.
|
||||||
|
*/
|
||||||
|
public class SpaceShipProject extends JavaPlugin {
|
||||||
|
|
||||||
|
private ConfigManager configManager;
|
||||||
|
private DatabaseManager databaseManager;
|
||||||
|
private RemoteManager remoteManager;
|
||||||
|
private SpaceshipManager spaceshipManager;
|
||||||
|
private ModelManager modelManager;
|
||||||
|
private EconomyHook economy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
getLogger().info("SpaceShipProject : demarrage...");
|
||||||
|
|
||||||
|
saveDefaultConfig();
|
||||||
|
configManager = new ConfigManager(this);
|
||||||
|
|
||||||
|
databaseManager = new DatabaseManager(this);
|
||||||
|
databaseManager.initialize();
|
||||||
|
|
||||||
|
economy = new EconomyHook(this);
|
||||||
|
economy.setup();
|
||||||
|
|
||||||
|
remoteManager = new RemoteManager(this);
|
||||||
|
modelManager = new ModelManager(this);
|
||||||
|
spaceshipManager = new SpaceshipManager(this);
|
||||||
|
|
||||||
|
getServer().getPluginManager().registerEvents(new RemoteListener(this), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new BlockProtectionListener(this), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
|
||||||
|
SpaceshipGUIListener.register(this);
|
||||||
|
|
||||||
|
SpaceShipCommand cmd = new SpaceShipCommand(this);
|
||||||
|
if (getCommand("spaceship") != null) {
|
||||||
|
getCommand("spaceship").setExecutor(cmd);
|
||||||
|
getCommand("spaceship").setTabCompleter(cmd);
|
||||||
|
}
|
||||||
|
SpaceShipAdminCommand adminCmd = new SpaceShipAdminCommand(this);
|
||||||
|
if (getCommand("spaceshipadmin") != null) {
|
||||||
|
getCommand("spaceshipadmin").setExecutor(adminCmd);
|
||||||
|
getCommand("spaceshipadmin").setTabCompleter(adminCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogger().info("SpaceShipProject est actif !");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
if (modelManager != null) {
|
||||||
|
try {
|
||||||
|
modelManager.unloadAllOnShutdown();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
getLogger().warning("Erreur shutdown ModelManager : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spaceshipManager != null) {
|
||||||
|
try {
|
||||||
|
spaceshipManager.unloadAllOnShutdown();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
getLogger().warning("Erreur lors du dechargement global : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (databaseManager != null) {
|
||||||
|
databaseManager.close();
|
||||||
|
}
|
||||||
|
getLogger().info("SpaceShipProject est arrete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigManager getConfigManager() { return configManager; }
|
||||||
|
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
||||||
|
public RemoteManager getRemoteManager() { return remoteManager; }
|
||||||
|
public SpaceshipManager getSpaceshipManager() { return spaceshipManager; }
|
||||||
|
public ModelManager getModelManager() { return modelManager; }
|
||||||
|
public EconomyHook getEconomy() { return economy; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Représentation en mémoire d'un spaceship.
|
||||||
|
* - id : identifiant unique en base
|
||||||
|
* - ownerUuid : propriétaire (seul lui peut modifier)
|
||||||
|
* - name : nom affiché sur la télécommande et dans les GUI
|
||||||
|
* - sizeX/Y/Z : dimensions de la boîte du spaceship
|
||||||
|
* - spawnX/Y/Z : point de spawn RELATIF à l'origine (téléportation sur le ship)
|
||||||
|
* - schematicJson : palette + blocs (RLE) sérialisés en JSON
|
||||||
|
* - settingsJson : paramètres modifiables par le joueur (JSON)
|
||||||
|
*/
|
||||||
|
public class Spaceship {
|
||||||
|
|
||||||
|
private final long id;
|
||||||
|
private final UUID ownerUuid;
|
||||||
|
private String name;
|
||||||
|
private int sizeX;
|
||||||
|
private int sizeY;
|
||||||
|
private int sizeZ;
|
||||||
|
private int spawnX;
|
||||||
|
private int spawnY;
|
||||||
|
private int spawnZ;
|
||||||
|
private String schematicJson;
|
||||||
|
private String settingsJson;
|
||||||
|
private int level;
|
||||||
|
private int money;
|
||||||
|
private int lastLoadedLevel;
|
||||||
|
|
||||||
|
public Spaceship(long id, UUID ownerUuid, String name,
|
||||||
|
int sizeX, int sizeY, int sizeZ,
|
||||||
|
int spawnX, int spawnY, int spawnZ,
|
||||||
|
String schematicJson, String settingsJson,
|
||||||
|
int level, int money, int lastLoadedLevel) {
|
||||||
|
this.id = id;
|
||||||
|
this.ownerUuid = ownerUuid;
|
||||||
|
this.name = name;
|
||||||
|
this.sizeX = sizeX;
|
||||||
|
this.sizeY = sizeY;
|
||||||
|
this.sizeZ = sizeZ;
|
||||||
|
this.spawnX = spawnX;
|
||||||
|
this.spawnY = spawnY;
|
||||||
|
this.spawnZ = spawnZ;
|
||||||
|
this.schematicJson = schematicJson;
|
||||||
|
this.settingsJson = settingsJson;
|
||||||
|
this.level = level;
|
||||||
|
this.money = money;
|
||||||
|
this.lastLoadedLevel = lastLoadedLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() { return id; }
|
||||||
|
public UUID getOwnerUuid() { return ownerUuid; }
|
||||||
|
public String getName() { return name; }
|
||||||
|
public void setName(String name) { this.name = name; }
|
||||||
|
public int getSizeX() { return sizeX; }
|
||||||
|
public int getSizeY() { return sizeY; }
|
||||||
|
public int getSizeZ() { return sizeZ; }
|
||||||
|
public void setSize(int x, int y, int z) { this.sizeX = x; this.sizeY = y; this.sizeZ = z; }
|
||||||
|
public int getSpawnX() { return spawnX; }
|
||||||
|
public int getSpawnY() { return spawnY; }
|
||||||
|
public int getSpawnZ() { return spawnZ; }
|
||||||
|
public void setSpawn(int x, int y, int z) { this.spawnX = x; this.spawnY = y; this.spawnZ = z; }
|
||||||
|
public String getSchematicJson() { return schematicJson; }
|
||||||
|
public void setSchematicJson(String schematicJson) { this.schematicJson = schematicJson; }
|
||||||
|
public String getSettingsJson() { return settingsJson; }
|
||||||
|
public void setSettingsJson(String settingsJson) { this.settingsJson = settingsJson; }
|
||||||
|
public int getLevel() { return level; }
|
||||||
|
public void setLevel(int level) { this.level = level; }
|
||||||
|
/** @deprecated La money est maintenant geree via Vault. Conserve uniquement pour compatibilite DB. */
|
||||||
|
@Deprecated public int getMoney() { return money; }
|
||||||
|
@Deprecated public void setMoney(int money) { this.money = money; }
|
||||||
|
public int getLastLoadedLevel() { return lastLoadedLevel; }
|
||||||
|
public void setLastLoadedLevel(int lastLoadedLevel) { this.lastLoadedLevel = lastLoadedLevel; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface de parametres d'un spaceship (ouverte via shift + clic droit sur la telecommande).
|
||||||
|
*
|
||||||
|
* Layout (27 slots) :
|
||||||
|
* - 4 : Info (tete : nom, taille, etat)
|
||||||
|
* - 11 : Invoquer / Decharger
|
||||||
|
* - 13 : Teleporter en haut / redescendre
|
||||||
|
* - 15 : Definir le spawn ici (uniquement si charge et joueur dessus)
|
||||||
|
* - 22 : Renommer le spaceship
|
||||||
|
* - 26 : Supprimer le spaceship (confirmation)
|
||||||
|
* - reste : verre de remplissage
|
||||||
|
*/
|
||||||
|
public class SpaceshipGUI implements SpaceshipGuiPanel {
|
||||||
|
|
||||||
|
private static final int SIZE = 36;
|
||||||
|
private static final int SLOT_INFO = 4;
|
||||||
|
private static final int SLOT_TOGGLE = 19;
|
||||||
|
private static final int SLOT_TELEPORT = 20;
|
||||||
|
private static final int SLOT_SET_SPAWN = 22;
|
||||||
|
private static final int SLOT_SPACE_JUMP = 24;
|
||||||
|
private static final int SLOT_UPGRADE = 25;
|
||||||
|
private static final int SLOT_RENAME = 31;
|
||||||
|
private static final int SLOT_DELETE = 35;
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final Player player;
|
||||||
|
private final Spaceship ship;
|
||||||
|
private final SpaceshipGUIHolder holder;
|
||||||
|
private Inventory inv;
|
||||||
|
|
||||||
|
public SpaceshipGUI(SpaceShipProject plugin, Player player, Spaceship ship) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.player = player;
|
||||||
|
this.ship = ship;
|
||||||
|
this.holder = new SpaceshipGUIHolder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
String title = ConfigManager.color("&8\u26F0 &bSpaceship &8\u2014 &f" + ship.getName());
|
||||||
|
inv = Bukkit.createInventory(holder, SIZE, title);
|
||||||
|
holder.setInventory(inv);
|
||||||
|
render();
|
||||||
|
SpaceshipGUIListener.registerOpen(player, this);
|
||||||
|
player.openInventory(inv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spaceship getShip() { return ship; }
|
||||||
|
public Player getPlayer() { return player; }
|
||||||
|
|
||||||
|
private void render() {
|
||||||
|
for (int i = 0; i < SIZE; i++) inv.setItem(i, filler());
|
||||||
|
|
||||||
|
SpaceshipManager mgr = plugin.getSpaceshipManager();
|
||||||
|
boolean loaded = mgr.isLoaded(ship.getId());
|
||||||
|
boolean onShip = loaded && mgr.isPlayerOnShip(player, ship);
|
||||||
|
|
||||||
|
inv.setItem(SLOT_INFO, infoItem(loaded));
|
||||||
|
|
||||||
|
inv.setItem(SLOT_TOGGLE, item(
|
||||||
|
loaded ? Material.REDSTONE_TORCH : Material.LEVER,
|
||||||
|
loaded ? "&c\u25A0 Decharger le spaceship" : "&a\u25B6 Invoquer le spaceship",
|
||||||
|
loaded
|
||||||
|
? List.of("&7Les modifications seront sauvegardees.")
|
||||||
|
: List.of("&7Charge le spaceship dans le ciel,",
|
||||||
|
"&7au-dessus du sol ou vous etes.")
|
||||||
|
));
|
||||||
|
|
||||||
|
inv.setItem(SLOT_TELEPORT, item(
|
||||||
|
Material.ENDER_PEARL,
|
||||||
|
onShip ? "&a\u2B0B Redescendre au sol" : "&b\u2B06 Teleporter sur le spaceship",
|
||||||
|
onShip
|
||||||
|
? List.of("&7Retourne a votre position au sol.")
|
||||||
|
: List.of("&7Vous teleporte sur le point de spawn",
|
||||||
|
"&7du spaceship (l'invoque si besoin).")
|
||||||
|
));
|
||||||
|
|
||||||
|
if (onShip) {
|
||||||
|
inv.setItem(SLOT_SET_SPAWN, item(
|
||||||
|
Material.RESPAWN_ANCHOR,
|
||||||
|
"&e\u2605 Definir le spawn ici",
|
||||||
|
List.of("&7Sauvegarde votre position actuelle",
|
||||||
|
"&7comme nouveau point d'apparition.")
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
inv.setItem(SLOT_SET_SPAWN, item(
|
||||||
|
Material.GRAY_DYE,
|
||||||
|
"&8Definir le spawn",
|
||||||
|
List.of("&8(Indisponible : vous n'etes pas",
|
||||||
|
"&8actuellement sur le spaceship)")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
double balance = eco.isReady() ? eco.getBalance(player.getUniqueId()) : 0;
|
||||||
|
double cost = mgr.upgradeCost(ship.getLevel());
|
||||||
|
boolean atMax = ship.getLevel() >= ShipSizing.maxUsefulLevel(plugin.getConfigManager());
|
||||||
|
boolean canAfford = eco.isReady() && balance >= cost;
|
||||||
|
int[] currentSize = {ship.getSizeX(), ship.getSizeY(), ship.getSizeZ()};
|
||||||
|
int[] nextSize = ShipSizing.sizeForLevel(plugin.getConfigManager(), ship.getLevel() + 1);
|
||||||
|
inv.setItem(SLOT_UPGRADE, item(
|
||||||
|
atMax ? Material.BARRIER : (canAfford ? Material.EXPERIENCE_BOTTLE : Material.GLASS_BOTTLE),
|
||||||
|
atMax
|
||||||
|
? "&8&l\u2728 Niveau maximum atteint"
|
||||||
|
: (canAfford ? "&a&l\u2728 Ameliorer (Lv " : "&7&l\u2728 Ameliorer (Lv ")
|
||||||
|
+ ship.getLevel() + " \u279C " + (ship.getLevel() + 1) + ")",
|
||||||
|
atMax ? List.of("&7Le ship est deja a sa taille maximale.")
|
||||||
|
: List.of(
|
||||||
|
"&7Niveau actuel : &e" + ship.getLevel(),
|
||||||
|
"&7Taille : &f" + currentSize[0] + "x" + currentSize[1] + "x" + currentSize[2]
|
||||||
|
+ " &7\u279C &f" + nextSize[0] + "x" + nextSize[1] + "x" + nextSize[2],
|
||||||
|
"&7Cout : &6" + (eco.isReady() ? eco.format(cost) : cost),
|
||||||
|
"&7Solde : &6" + (eco.isReady() ? eco.format(balance) : "-"),
|
||||||
|
"",
|
||||||
|
canAfford ? "&aClic &7pour ameliorer." : "&cFonds insuffisants."
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Bouton Saut Spatial : actif uniquement quand le joueur est sur le ship.
|
||||||
|
int jumpMax = mgr.spaceJumpMax(ship.getLevel());
|
||||||
|
if (onShip) {
|
||||||
|
inv.setItem(SLOT_SPACE_JUMP, item(
|
||||||
|
Material.ENDER_EYE,
|
||||||
|
"&d&l\u27FC Saut Spatial",
|
||||||
|
List.of(
|
||||||
|
"&7Deplace le spaceship le long d'un axe.",
|
||||||
|
"&7Distance max : &f" + jumpMax + " &7blocs",
|
||||||
|
"&7Cout : &6" + (eco.isReady() ? eco.format(1) : "1") + " &7par bloc",
|
||||||
|
"",
|
||||||
|
"&eClic pour ouvrir le panneau de saut."
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
inv.setItem(SLOT_SPACE_JUMP, item(
|
||||||
|
Material.GRAY_DYE,
|
||||||
|
"&8\u27FC Saut Spatial",
|
||||||
|
List.of("&8(Indisponible : vous n'etes pas",
|
||||||
|
"&8actuellement sur le spaceship)")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
inv.setItem(SLOT_RENAME, item(
|
||||||
|
Material.NAME_TAG,
|
||||||
|
"&e\u270F Renommer",
|
||||||
|
List.of("&7Tapez le nouveau nom dans le chat.",
|
||||||
|
"&7Tapez &ccancel &7pour annuler.")
|
||||||
|
));
|
||||||
|
|
||||||
|
inv.setItem(SLOT_DELETE, item(
|
||||||
|
Material.BARRIER,
|
||||||
|
"&c\u2620 Supprimer le spaceship",
|
||||||
|
List.of("&7Suppression definitive (telecommande",
|
||||||
|
"&7incluse). Decharge le ship d'abord.",
|
||||||
|
"&cShift + clic pour confirmer.")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack infoItem(boolean loaded) {
|
||||||
|
ItemStack head = new ItemStack(Material.NETHER_STAR);
|
||||||
|
ItemMeta meta = head.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ConfigManager.color("&b&l\u2730 &f" + ship.getName()));
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
double balance = eco.isReady() ? eco.getBalance(player.getUniqueId()) : 0;
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ConfigManager.color("&7ID : &f#" + ship.getId()));
|
||||||
|
lore.add(ConfigManager.color("&7Niveau : &e" + ship.getLevel()));
|
||||||
|
lore.add(ConfigManager.color("&7Votre solde : &6" + (eco.isReady() ? eco.format(balance) : "-")));
|
||||||
|
lore.add(ConfigManager.color("&7Taille : &f"
|
||||||
|
+ ship.getSizeX() + "x" + ship.getSizeY() + "x" + ship.getSizeZ()));
|
||||||
|
lore.add(ConfigManager.color("&7Spawn (rel.) : &f"
|
||||||
|
+ ship.getSpawnX() + ", " + ship.getSpawnY() + ", " + ship.getSpawnZ()));
|
||||||
|
lore.add(ConfigManager.color("&7Statut : " + (loaded ? "&aCHARGE" : "&7decharge")));
|
||||||
|
meta.setLore(lore);
|
||||||
|
head.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack filler() {
|
||||||
|
ItemStack stack = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
|
||||||
|
ItemMeta meta = stack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(" ");
|
||||||
|
stack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack item(Material mat, String name, List<String> lore) {
|
||||||
|
ItemStack stack = new ItemStack(mat);
|
||||||
|
ItemMeta meta = stack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ConfigManager.color(name));
|
||||||
|
List<String> colored = new ArrayList<>(lore.size());
|
||||||
|
for (String s : lore) colored.add(ConfigManager.color(s));
|
||||||
|
meta.setLore(colored);
|
||||||
|
stack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleClick(InventoryClickEvent event) {
|
||||||
|
int slot = event.getRawSlot();
|
||||||
|
SpaceshipManager mgr = plugin.getSpaceshipManager();
|
||||||
|
|
||||||
|
switch (slot) {
|
||||||
|
case SLOT_TOGGLE -> {
|
||||||
|
if (mgr.isLoaded(ship.getId())) {
|
||||||
|
if (mgr.isPlayerOnShip(player, ship)) {
|
||||||
|
mgr.teleportDown(player, ship);
|
||||||
|
}
|
||||||
|
mgr.unload(player, ship);
|
||||||
|
} else {
|
||||||
|
mgr.summon(player, ship);
|
||||||
|
}
|
||||||
|
player.closeInventory();
|
||||||
|
}
|
||||||
|
case SLOT_TELEPORT -> {
|
||||||
|
if (!mgr.isLoaded(ship.getId())) {
|
||||||
|
if (mgr.summon(player, ship)) mgr.teleportToShip(player, ship);
|
||||||
|
} else if (mgr.isPlayerOnShip(player, ship)) {
|
||||||
|
mgr.teleportDown(player, ship);
|
||||||
|
} else {
|
||||||
|
mgr.teleportToShip(player, ship);
|
||||||
|
}
|
||||||
|
player.closeInventory();
|
||||||
|
}
|
||||||
|
case SLOT_SET_SPAWN -> {
|
||||||
|
if (!mgr.isLoaded(ship.getId()) || !mgr.isPlayerOnShip(player, ship)) return;
|
||||||
|
LoadedSpaceship loaded = mgr.getLoaded(ship.getId());
|
||||||
|
if (loaded == null) return;
|
||||||
|
int rx = player.getLocation().getBlockX() - loaded.getOriginX();
|
||||||
|
int ry = player.getLocation().getBlockY() - loaded.getOriginY();
|
||||||
|
int rz = player.getLocation().getBlockZ() - loaded.getOriginZ();
|
||||||
|
ship.setSpawn(rx, ry, rz);
|
||||||
|
plugin.getDatabaseManager().updateSpaceship(ship);
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&aNouveau spawn enregistre."));
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
case SLOT_UPGRADE -> {
|
||||||
|
double cost = mgr.upgradeCost(ship.getLevel());
|
||||||
|
if (mgr.upgradeLevel(player, ship)) {
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
String costStr = eco.isReady() ? eco.format(cost) : String.valueOf(cost);
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&aSpaceship ameliore au niveau &e" + ship.getLevel() + " &a(-&6" + costStr + "&a)."));
|
||||||
|
refreshHandRemote();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SLOT_SPACE_JUMP -> {
|
||||||
|
if (mgr.isPlayerOnShip(player, ship)) {
|
||||||
|
new SpaceJumpGUI(plugin, player, ship).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SLOT_RENAME -> {
|
||||||
|
SpaceshipGUIListener.registerPendingRename(player, ship);
|
||||||
|
player.closeInventory();
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&eTapez le nouveau nom dans le chat (&ccancel&e pour annuler)."));
|
||||||
|
}
|
||||||
|
case SLOT_DELETE -> {
|
||||||
|
if (!event.isShiftClick()) {
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&cShift + clic pour confirmer la suppression."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mgr.delete(ship);
|
||||||
|
player.sendMessage(plugin.getConfigManager().msgDeleted());
|
||||||
|
player.closeInventory();
|
||||||
|
}
|
||||||
|
default -> { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshHandRemote() {
|
||||||
|
ItemStack mainHand = player.getInventory().getItemInMainHand();
|
||||||
|
if (plugin.getRemoteManager().isRemote(mainHand)
|
||||||
|
&& plugin.getRemoteManager().getShipId(mainHand) == ship.getId()) {
|
||||||
|
plugin.getRemoteManager().refreshRemote(mainHand, ship);
|
||||||
|
player.getInventory().setItemInMainHand(mainHand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
|
||||||
|
/** Holder generique pour les panneaux GUI du plugin (Spaceship + SpaceJump). */
|
||||||
|
public class SpaceshipGUIHolder implements InventoryHolder {
|
||||||
|
|
||||||
|
private final SpaceshipGuiPanel panel;
|
||||||
|
private Inventory inventory;
|
||||||
|
|
||||||
|
public SpaceshipGUIHolder(SpaceshipGuiPanel panel) {
|
||||||
|
this.panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInventory(Inventory inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpaceshipGuiPanel getPanel() {
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/** Listener partage pour les SpaceshipGUI : clics, fermeture, et renommage par chat. */
|
||||||
|
public class SpaceshipGUIListener implements Listener {
|
||||||
|
|
||||||
|
private static final Map<UUID, SpaceshipGuiPanel> OPEN_GUIS = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<UUID, Long> PENDING_RENAME = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
|
||||||
|
private SpaceshipGUIListener(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(SpaceShipProject plugin) {
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(new SpaceshipGUIListener(plugin), plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerOpen(Player player, SpaceshipGUI gui) {
|
||||||
|
OPEN_GUIS.put(player.getUniqueId(), gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerOpenJump(Player player, SpaceJumpGUI gui) {
|
||||||
|
OPEN_GUIS.put(player.getUniqueId(), gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerPendingRename(Player player, Spaceship ship) {
|
||||||
|
PENDING_RENAME.put(player.getUniqueId(), ship.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||||
|
Inventory top = event.getView().getTopInventory();
|
||||||
|
if (!(top.getHolder() instanceof SpaceshipGUIHolder holder)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (event.getClickedInventory() != null && event.getClickedInventory() == top) {
|
||||||
|
holder.getPanel().handleClick(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
|
||||||
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
|
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||||
|
Inventory top = event.getView().getTopInventory();
|
||||||
|
if (!(top.getHolder() instanceof SpaceshipGUIHolder)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
|
if (!(event.getPlayer() instanceof Player player)) return;
|
||||||
|
if (event.getInventory().getHolder() instanceof SpaceshipGUIHolder) {
|
||||||
|
OPEN_GUIS.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH)
|
||||||
|
public void onChat(AsyncChatEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
Long shipId = PENDING_RENAME.remove(player.getUniqueId());
|
||||||
|
if (shipId == null) return;
|
||||||
|
|
||||||
|
String message = PlainTextComponentSerializer.plainText().serialize(event.message()).trim();
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
if (message.equalsIgnoreCase("cancel") || message.isEmpty()) {
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&7Renommage annule."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String clean = message.length() > 32 ? message.substring(0, 32) : message;
|
||||||
|
Spaceship ship = plugin.getDatabaseManager().getSpaceship(shipId);
|
||||||
|
if (ship == null || !ship.getOwnerUuid().equals(player.getUniqueId())) {
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&cImpossible de renommer ce spaceship."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ship.setName(clean);
|
||||||
|
plugin.getDatabaseManager().updateSpaceship(ship);
|
||||||
|
|
||||||
|
// Met a jour la telecommande dans la main si elle est presente
|
||||||
|
ItemStack mainHand = player.getInventory().getItemInMainHand();
|
||||||
|
if (plugin.getRemoteManager().isRemote(mainHand)
|
||||||
|
&& mainHand.getItemMeta() != null
|
||||||
|
&& shipId.equals(mainHand.getItemMeta().getPersistentDataContainer()
|
||||||
|
.get(plugin.getRemoteManager().getShipIdKey(), PersistentDataType.LONG))) {
|
||||||
|
plugin.getRemoteManager().refreshRemote(mainHand, ship);
|
||||||
|
player.getInventory().setItemInMainHand(mainHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage(plugin.getConfigManager().prefix()
|
||||||
|
+ ConfigManager.color("&aSpaceship renomme : &b" + clean));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
|
||||||
|
/** Interface commune aux differents panneaux de GUI du plugin. */
|
||||||
|
public interface SpaceshipGuiPanel {
|
||||||
|
void handleClick(InventoryClickEvent event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,605 @@
|
|||||||
|
package com.spaceshipproject;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logique metier des spaceships : creation, invocation, decharge, teleportation.
|
||||||
|
*
|
||||||
|
* Etat en memoire :
|
||||||
|
* - returnLocations : ou redescendre le joueur quand il se trouve sur son ship.
|
||||||
|
*/
|
||||||
|
public class SpaceshipManager {
|
||||||
|
|
||||||
|
private final SpaceShipProject plugin;
|
||||||
|
private final DatabaseManager db;
|
||||||
|
private final ConfigManager cfg;
|
||||||
|
|
||||||
|
/** Position au sol memorisee lors de la teleportation vers un ship (par joueur). */
|
||||||
|
private final Map<UUID, Location> returnLocations = new HashMap<>();
|
||||||
|
|
||||||
|
public SpaceshipManager(SpaceShipProject plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.db = plugin.getDatabaseManager();
|
||||||
|
this.cfg = plugin.getConfigManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ CREATION ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cree un nouveau spaceship en base avec :
|
||||||
|
* - le modele "default.json" du dossier models/ s'il existe,
|
||||||
|
* - sinon la plateforme procedurale par defaut.
|
||||||
|
* Retourne l'id ou -1 si le quota est atteint.
|
||||||
|
*/
|
||||||
|
public long createNewSpaceship(Player owner) {
|
||||||
|
int max = cfg.maxShipsPerPlayer();
|
||||||
|
int count = db.countSpaceshipsByOwner(owner.getUniqueId());
|
||||||
|
if (max > 0 && count >= max) {
|
||||||
|
owner.sendMessage(cfg.msgMaxShips(max));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
db.upsertPlayer(owner);
|
||||||
|
|
||||||
|
String schematic = plugin.getModelManager().loadDefaultModelJson();
|
||||||
|
int sx, sy, sz;
|
||||||
|
if (schematic != null) {
|
||||||
|
int[] size = SchematicHelper.readSize(schematic);
|
||||||
|
sx = size[0]; sy = size[1]; sz = size[2];
|
||||||
|
} else {
|
||||||
|
sx = cfg.defaultSizeX();
|
||||||
|
sy = cfg.defaultSizeY();
|
||||||
|
sz = cfg.defaultSizeZ();
|
||||||
|
schematic = SchematicHelper.buildDefaultSchematic(sx, sy, sz,
|
||||||
|
cfg.defaultPlatformMaterial(),
|
||||||
|
cfg.defaultEdgeMaterial(),
|
||||||
|
cfg.defaultGlassMaterial());
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] spawn = SchematicHelper.readSpawn(schematic);
|
||||||
|
String name = cfg.defaultShipName().replace("%n%", String.valueOf(count + 1));
|
||||||
|
return db.createSpaceship(owner.getUniqueId(), name, sx, sy, sz,
|
||||||
|
spawn[0], spawn[1], spawn[2], schematic, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ STATE ============================ */
|
||||||
|
|
||||||
|
public boolean isLoaded(long shipId) {
|
||||||
|
return db.getLoaded(shipId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoadedSpaceship getLoaded(long shipId) {
|
||||||
|
return db.getLoaded(shipId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ SUMMON ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoque le spaceship au-dessus du joueur. Si deja charge, ne fait rien.
|
||||||
|
* Coute {@link ConfigManager#costSummon()} en monnaie Vault (paye par le proprietaire).
|
||||||
|
*/
|
||||||
|
public boolean summon(Player player, Spaceship ship) {
|
||||||
|
if (isLoaded(ship.getId())) {
|
||||||
|
player.sendMessage(cfg.msgAlreadyLoaded());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paiement (sauf admin).
|
||||||
|
double cost = cfg.costSummon();
|
||||||
|
if (cost > 0 && !player.hasPermission("spaceship.bypass.cost")) {
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
if (!eco.isReady()) {
|
||||||
|
player.sendMessage(cfg.msgVaultUnavailable());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double balance = eco.getBalance(player.getUniqueId());
|
||||||
|
if (balance < cost) {
|
||||||
|
player.sendMessage(cfg.msgNotEnoughMoney(eco.format(cost), eco.format(balance)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
// Si le niveau a augmente depuis le dernier chargement, on etend la schematique
|
||||||
|
// a la nouvelle taille en marquant les coins avec Verdant Froglight.
|
||||||
|
if (ship.getLevel() > ship.getLastLoadedLevel()) {
|
||||||
|
int[] target = ShipSizing.sizeForLevel(cfg, ship.getLevel());
|
||||||
|
int nx = Math.max(target[0], ship.getSizeX());
|
||||||
|
int ny = Math.max(target[1], ship.getSizeY());
|
||||||
|
int nz = Math.max(target[2], ship.getSizeZ());
|
||||||
|
if (nx != ship.getSizeX() || ny != ship.getSizeY() || nz != ship.getSizeZ()) {
|
||||||
|
try {
|
||||||
|
String expanded = SchematicHelper.expandSchematic(
|
||||||
|
ship.getSchematicJson(), nx, ny, nz, Material.VERDANT_FROGLIGHT);
|
||||||
|
ship.setSchematicJson(expanded);
|
||||||
|
ship.setSize(nx, ny, nz);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().warning("Erreur expansion ship #" + ship.getId() + " : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ship.setLastLoadedLevel(ship.getLevel());
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sx = ship.getSizeX(), sy = ship.getSizeY(), sz = ship.getSizeZ();
|
||||||
|
int ox = player.getLocation().getBlockX() - sx / 2;
|
||||||
|
int oz = player.getLocation().getBlockZ() - sz / 2;
|
||||||
|
int oy = clampY(world, player.getLocation().getBlockY() + cfg.summonOffsetY(), sy);
|
||||||
|
|
||||||
|
if (cfg.checkCollision() && SchematicHelper.hasCollision(world, ox, oy, oz, sx, sy, sz)) {
|
||||||
|
player.sendMessage(cfg.msgCollision());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SchematicHelper.restoreRegion(ship.getSchematicJson(), world, ox, oy, oz);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().severe("Erreur restauration ship #" + ship.getId() + " : " + ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debit apres succes de la restauration.
|
||||||
|
if (cost > 0 && !player.hasPermission("spaceship.bypass.cost")) {
|
||||||
|
plugin.getEconomy().withdraw(player.getUniqueId(), cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.markLoaded(ship.getId(), world.getName(), ox, oy, oz);
|
||||||
|
player.sendMessage(cfg.msgSummoned(ship.getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ UNLOAD ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture les modifications et decharge le ship du monde.
|
||||||
|
* Sauvegarde la nouvelle schematique en base.
|
||||||
|
*/
|
||||||
|
public boolean unload(Player player, Spaceship ship) {
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded == null) {
|
||||||
|
player.sendMessage(cfg.msgNotLoaded());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
plugin.getLogger().warning("Monde introuvable pour decharger le ship #" + ship.getId());
|
||||||
|
db.markUnloaded(ship.getId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String updated = SchematicHelper.captureRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ(),
|
||||||
|
ship.getSpawnX(), ship.getSpawnY(), ship.getSpawnZ());
|
||||||
|
ship.setSchematicJson(updated);
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().severe("Erreur capture ship #" + ship.getId() + " : " + ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SchematicHelper.clearRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ());
|
||||||
|
|
||||||
|
db.markUnloaded(ship.getId());
|
||||||
|
player.sendMessage(cfg.msgUnloaded(ship.getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ TELEPORT ============================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teleporte le joueur sur le spawn du ship (le ship doit etre charge).
|
||||||
|
* Memorise la position de retour.
|
||||||
|
*/
|
||||||
|
public boolean teleportToShip(Player player, Spaceship ship) {
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded == null) {
|
||||||
|
player.sendMessage(cfg.msgNotLoaded());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) return false;
|
||||||
|
|
||||||
|
returnLocations.put(player.getUniqueId(), player.getLocation().clone());
|
||||||
|
|
||||||
|
Location target = new Location(world,
|
||||||
|
loaded.getOriginX() + ship.getSpawnX() + 0.5,
|
||||||
|
loaded.getOriginY() + ship.getSpawnY(),
|
||||||
|
loaded.getOriginZ() + ship.getSpawnZ() + 0.5,
|
||||||
|
player.getLocation().getYaw(),
|
||||||
|
player.getLocation().getPitch());
|
||||||
|
player.teleport(target);
|
||||||
|
player.sendMessage(cfg.msgTpUp(ship.getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teleporte le joueur a sa position de retour. Si pas memorise,
|
||||||
|
* tombe sur le sol au-dessous du ship.
|
||||||
|
*/
|
||||||
|
public void teleportDown(Player player, Spaceship ship) {
|
||||||
|
Location stored = returnLocations.remove(player.getUniqueId());
|
||||||
|
if (stored != null && stored.getWorld() != null) {
|
||||||
|
player.teleport(stored);
|
||||||
|
} else {
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded != null && loaded.getWorld() != null) {
|
||||||
|
int x = loaded.getOriginX() + ship.getSizeX() / 2;
|
||||||
|
int z = loaded.getOriginZ() + ship.getSizeZ() / 2;
|
||||||
|
int y = loaded.getWorld().getHighestBlockYAt(x, z) + 1;
|
||||||
|
player.teleport(new Location(loaded.getWorld(), x + 0.5, y, z + 0.5,
|
||||||
|
player.getLocation().getYaw(), player.getLocation().getPitch()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.sendMessage(cfg.msgTpDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True si le joueur se trouve actuellement DANS la bounding box du ship charge.
|
||||||
|
*/
|
||||||
|
public boolean isPlayerOnShip(Player player, Spaceship ship) {
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded == null) return false;
|
||||||
|
return loaded.contains(player.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ DELETE ============================ */
|
||||||
|
|
||||||
|
public void delete(Spaceship ship) {
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded != null && loaded.getWorld() != null) {
|
||||||
|
// Si le proprietaire est sur le ship, le tp au sol avant la disparition.
|
||||||
|
OfflinePlayer off = Bukkit.getOfflinePlayer(ship.getOwnerUuid());
|
||||||
|
if (off.isOnline()) {
|
||||||
|
Player onlineOwner = off.getPlayer();
|
||||||
|
if (onlineOwner != null && loaded.contains(onlineOwner.getLocation())) {
|
||||||
|
teleportDown(onlineOwner, ship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SchematicHelper.clearRegion(loaded.getWorld(),
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ());
|
||||||
|
}
|
||||||
|
db.deleteSpaceship(ship.getId());
|
||||||
|
// Supprime egalement la telecommande dans l'inventaire (si proprietaire en ligne).
|
||||||
|
removeRemoteFromInventory(ship.getOwnerUuid(), ship.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Supprime toutes les boussoles correspondant a shipId dans l'inventaire du joueur (s'il est en ligne). */
|
||||||
|
public int removeRemoteFromInventory(UUID ownerUuid, long shipId) {
|
||||||
|
Player p = Bukkit.getPlayer(ownerUuid);
|
||||||
|
if (p == null) return 0;
|
||||||
|
return removeRemotesFromInventory(p.getInventory(), shipId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int removeRemotesFromInventory(PlayerInventory inv, long shipId) {
|
||||||
|
int removed = 0;
|
||||||
|
RemoteManager rm = plugin.getRemoteManager();
|
||||||
|
for (int i = 0; i < inv.getSize(); i++) {
|
||||||
|
ItemStack it = inv.getItem(i);
|
||||||
|
if (it == null || !rm.isRemote(it)) continue;
|
||||||
|
Long id = it.getItemMeta().getPersistentDataContainer().get(rm.getShipIdKey(), PersistentDataType.LONG);
|
||||||
|
if (id != null && id == shipId) {
|
||||||
|
inv.setItem(i, null);
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcilie l'inventaire du joueur :
|
||||||
|
* - supprime les telecommandes dont le ship n'existe plus,
|
||||||
|
* - supprime les doublons (>1 stack pour le meme shipId).
|
||||||
|
* Renvoie le nombre d'items supprimes.
|
||||||
|
*/
|
||||||
|
public int sanitizePlayerRemotes(Player player) {
|
||||||
|
RemoteManager rm = plugin.getRemoteManager();
|
||||||
|
PlayerInventory inv = player.getInventory();
|
||||||
|
Map<Long, Integer> seen = new HashMap<>();
|
||||||
|
int removed = 0;
|
||||||
|
for (int i = 0; i < inv.getSize(); i++) {
|
||||||
|
ItemStack it = inv.getItem(i);
|
||||||
|
if (it == null || !rm.isRemote(it)) continue;
|
||||||
|
Long id = it.getItemMeta().getPersistentDataContainer().get(rm.getShipIdKey(), PersistentDataType.LONG);
|
||||||
|
if (id == null) { inv.setItem(i, null); removed++; continue; }
|
||||||
|
if (db.getSpaceship(id) == null) {
|
||||||
|
inv.setItem(i, null); removed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Integer firstSlot = seen.put(id, i);
|
||||||
|
if (firstSlot != null) {
|
||||||
|
// Doublon : on retire l'item en cours et on garde le premier rencontre.
|
||||||
|
inv.setItem(i, null);
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decharge (capture + clear) tous les spaceships actuellement charges
|
||||||
|
* appartenant au joueur. Utilise a la deconnexion.
|
||||||
|
*/
|
||||||
|
public int unloadAllForPlayer(UUID ownerUuid) {
|
||||||
|
int count = 0;
|
||||||
|
for (LoadedSpaceship loaded : db.getLoadedByOwner(ownerUuid)) {
|
||||||
|
Spaceship ship = db.getSpaceship(loaded.getSpaceshipId());
|
||||||
|
if (ship == null) {
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String updated = SchematicHelper.captureRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ(),
|
||||||
|
ship.getSpawnX(), ship.getSpawnY(), ship.getSpawnZ());
|
||||||
|
ship.setSchematicJson(updated);
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
SchematicHelper.clearRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ());
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
count++;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().warning("Erreur unloadAllForPlayer ship #" + ship.getId() + " : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ LEVEL / MONEY ============================ */
|
||||||
|
|
||||||
|
/** Cout pour passer du level N a N+1 : upgrade-base * N. */
|
||||||
|
public double upgradeCost(int currentLevel) {
|
||||||
|
return Math.max(0, currentLevel) * cfg.costUpgradeBase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tente d'ameliorer le ship d'un niveau (paye avec Vault par le proprietaire {@code payer}).
|
||||||
|
* La taille effective grimpe selon la regle de progression mais le ship n'est physiquement
|
||||||
|
* agrandi qu'au prochain summon (avec marquage des coins).
|
||||||
|
* Renvoie true si l'achat est effectue.
|
||||||
|
*/
|
||||||
|
public boolean upgradeLevel(Player payer, Spaceship ship) {
|
||||||
|
if (ship.getLevel() >= ShipSizing.maxUsefulLevel(cfg)) return false;
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
if (!eco.isReady()) { payer.sendMessage(cfg.msgVaultUnavailable()); return false; }
|
||||||
|
double cost = upgradeCost(ship.getLevel());
|
||||||
|
if (!eco.has(payer.getUniqueId(), cost)) {
|
||||||
|
payer.sendMessage(cfg.msgNotEnoughMoney(eco.format(cost), eco.format(eco.getBalance(payer.getUniqueId()))));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!eco.withdraw(payer.getUniqueId(), cost)) return false;
|
||||||
|
ship.setLevel(ship.getLevel() + 1);
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ SAUT SPATIAL ============================ */
|
||||||
|
|
||||||
|
/** Distance maximale d'un saut spatial pour le niveau donne. */
|
||||||
|
public int spaceJumpMax(int level) {
|
||||||
|
return Math.min(cfg.spaceJumpHardCap(), Math.max(cfg.spaceJumpMin(), cfg.spaceJumpBlocksPerLevel() * Math.max(1, level)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cout d'un saut spatial. */
|
||||||
|
public double spaceJumpCost(int distance) {
|
||||||
|
return Math.max(0, distance) * cfg.costSpaceJumpPerBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effectue un saut spatial : decharge le ship a son emplacement courant,
|
||||||
|
* le recharge a la nouvelle position (delta sur un axe), teleporte le joueur sur le ship.
|
||||||
|
*
|
||||||
|
* @param axis 'X', 'Y' ou 'Z'
|
||||||
|
* @param signedDistance distance signee (negatif = direction opposee)
|
||||||
|
*/
|
||||||
|
public boolean spaceJump(Player player, Spaceship ship, char axis, int signedDistance) {
|
||||||
|
if (!ship.getOwnerUuid().equals(player.getUniqueId()) && !player.hasPermission("spaceship.admin")) {
|
||||||
|
player.sendMessage(cfg.msgNotOwner());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LoadedSpaceship loaded = db.getLoaded(ship.getId());
|
||||||
|
if (loaded == null) { player.sendMessage(cfg.msgNotLoaded()); return false; }
|
||||||
|
if (!isPlayerOnShip(player, ship)) { player.sendMessage(cfg.msgSpaceJumpNotOnShip()); return false; }
|
||||||
|
|
||||||
|
int absDist = Math.abs(signedDistance);
|
||||||
|
int max = spaceJumpMax(ship.getLevel());
|
||||||
|
if (absDist < cfg.spaceJumpMin() || absDist > max) {
|
||||||
|
player.sendMessage(cfg.msgSpaceJumpTooFar(max, ship.getLevel()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double cost = spaceJumpCost(absDist);
|
||||||
|
EconomyHook eco = plugin.getEconomy();
|
||||||
|
if (cost > 0) {
|
||||||
|
if (!eco.isReady()) { player.sendMessage(cfg.msgVaultUnavailable()); return false; }
|
||||||
|
if (!eco.has(player.getUniqueId(), cost)) {
|
||||||
|
player.sendMessage(cfg.msgNotEnoughMoney(eco.format(cost), eco.format(eco.getBalance(player.getUniqueId()))));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) return false;
|
||||||
|
|
||||||
|
// Calcule la nouvelle origine.
|
||||||
|
int nx = loaded.getOriginX(), ny = loaded.getOriginY(), nz = loaded.getOriginZ();
|
||||||
|
switch (Character.toUpperCase(axis)) {
|
||||||
|
case 'X' -> nx += signedDistance;
|
||||||
|
case 'Y' -> ny += signedDistance;
|
||||||
|
case 'Z' -> nz += signedDistance;
|
||||||
|
default -> { player.sendMessage(ConfigManager.color("&cAxe invalide.")); return false; }
|
||||||
|
}
|
||||||
|
ny = clampY(world, ny, ship.getSizeY());
|
||||||
|
|
||||||
|
if (cfg.checkCollision() && SchematicHelper.hasCollision(world, nx, ny, nz,
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ())) {
|
||||||
|
player.sendMessage(cfg.msgCollision());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Capture l'etat actuel
|
||||||
|
try {
|
||||||
|
String updated = SchematicHelper.captureRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ(),
|
||||||
|
ship.getSpawnX(), ship.getSpawnY(), ship.getSpawnZ());
|
||||||
|
ship.setSchematicJson(updated);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
player.sendMessage(ConfigManager.color("&cErreur capture : " + ex.getMessage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 2) Clear emplacement initial
|
||||||
|
SchematicHelper.clearRegion(world, loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ());
|
||||||
|
// 3) Restore au nouvel emplacement
|
||||||
|
try {
|
||||||
|
SchematicHelper.restoreRegion(ship.getSchematicJson(), world, nx, ny, nz);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Rollback : recharge a l'ancien emplacement
|
||||||
|
try {
|
||||||
|
SchematicHelper.restoreRegion(ship.getSchematicJson(), world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
player.sendMessage(ConfigManager.color("&cErreur restauration : " + ex.getMessage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
db.markLoaded(ship.getId(), world.getName(), nx, ny, nz);
|
||||||
|
if (cost > 0) eco.withdraw(player.getUniqueId(), cost);
|
||||||
|
|
||||||
|
// Teleporte le joueur sur le ship a son spawn.
|
||||||
|
Location target = new Location(world,
|
||||||
|
nx + ship.getSpawnX() + 0.5,
|
||||||
|
ny + ship.getSpawnY(),
|
||||||
|
nz + ship.getSpawnZ() + 0.5,
|
||||||
|
player.getLocation().getYaw(), player.getLocation().getPitch());
|
||||||
|
player.teleport(target);
|
||||||
|
|
||||||
|
String dir = axisLabel(axis, signedDistance);
|
||||||
|
player.sendMessage(cfg.msgSpaceJumpSuccess(absDist, dir, eco.isReady() ? eco.format(cost) : String.valueOf(cost)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String axisLabel(char axis, int signed) {
|
||||||
|
return switch (Character.toUpperCase(axis)) {
|
||||||
|
case 'X' -> signed >= 0 ? "Est (X+)" : "Ouest (X-)";
|
||||||
|
case 'Y' -> signed >= 0 ? "Haut (Y+)" : "Bas (Y-)";
|
||||||
|
case 'Z' -> signed >= 0 ? "Sud (Z+)" : "Nord (Z-)";
|
||||||
|
default -> "?";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================ HELPERS ============================ */
|
||||||
|
|
||||||
|
private int clampY(World world, int y, int sizeY) {
|
||||||
|
int min = world.getMinHeight() + 1;
|
||||||
|
int max = world.getMaxHeight() - sizeY - 1;
|
||||||
|
return Math.max(min, Math.min(max, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le LoadedSpaceship et le Spaceship si le joueur se tient sur l'un de SES ships.
|
||||||
|
* Pratique pour les listeners de protection.
|
||||||
|
*/
|
||||||
|
public Spaceship findOwnedShipPlayerIsOn(Player player) {
|
||||||
|
List<Spaceship> ships = db.getSpaceshipsByOwner(player.getUniqueId());
|
||||||
|
for (Spaceship s : ships) {
|
||||||
|
if (isPlayerOnShip(player, s)) return s;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le proprietaire du ship dont la bounding box contient cette position,
|
||||||
|
* ou null sinon. Utilise par la protection de blocs.
|
||||||
|
*/
|
||||||
|
public LoadedShipOwnerInfo findShipOwnerAt(World world, int x, int y, int z) {
|
||||||
|
for (LoadedSpaceship loaded : db.getAllLoaded()) {
|
||||||
|
if (!loaded.getWorldName().equals(world.getName())) continue;
|
||||||
|
if (x < loaded.getOriginX() || x >= loaded.getOriginX() + loaded.getSizeX()) continue;
|
||||||
|
if (y < loaded.getOriginY() || y >= loaded.getOriginY() + loaded.getSizeY()) continue;
|
||||||
|
if (z < loaded.getOriginZ() || z >= loaded.getOriginZ() + loaded.getSizeZ()) continue;
|
||||||
|
Spaceship ship = db.getSpaceship(loaded.getSpaceshipId());
|
||||||
|
if (ship == null) continue;
|
||||||
|
return new LoadedShipOwnerInfo(ship, loaded);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Petit conteneur pour la protection. */
|
||||||
|
public record LoadedShipOwnerInfo(Spaceship ship, LoadedSpaceship loaded) {
|
||||||
|
public UUID ownerUuid() { return ship.getOwnerUuid(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decharge tous les ships en base lors de la fermeture du serveur.
|
||||||
|
* (Pour eviter qu'au prochain demarrage le ship soit considere comme charge alors qu'il a ete genere a un endroit qui sera reset).
|
||||||
|
* On capture l'etat actuel pour ne rien perdre.
|
||||||
|
*/
|
||||||
|
public void unloadAllOnShutdown() {
|
||||||
|
for (LoadedSpaceship loaded : db.getAllLoaded()) {
|
||||||
|
Spaceship ship = db.getSpaceship(loaded.getSpaceshipId());
|
||||||
|
if (ship == null) {
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
World world = loaded.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String updated = SchematicHelper.captureRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ(),
|
||||||
|
ship.getSpawnX(), ship.getSpawnY(), ship.getSpawnZ());
|
||||||
|
ship.setSchematicJson(updated);
|
||||||
|
db.updateSpaceship(ship);
|
||||||
|
SchematicHelper.clearRegion(world,
|
||||||
|
loaded.getOriginX(), loaded.getOriginY(), loaded.getOriginZ(),
|
||||||
|
ship.getSizeX(), ship.getSizeY(), ship.getSizeZ());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().warning("Erreur shutdown ship #" + ship.getId() + " : " + ex.getMessage());
|
||||||
|
}
|
||||||
|
db.markUnloaded(loaded.getSpaceshipId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifie en boucle si des joueurs sont tombes du ship et corrige eventuellement.
|
||||||
|
* Pour l'instant ne fait rien, methode reservee a une evolution future.
|
||||||
|
*/
|
||||||
|
public void tick() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseManager getDatabase() { return db; }
|
||||||
|
|
||||||
|
/** Test pratique : Bukkit#getWorld. */
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static World worldOf(String name) { return Bukkit.getWorld(name); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# =========================================================
|
||||||
|
# SpaceShipProject - Configuration
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# Limite de spaceships par joueur (0 = illimite)
|
||||||
|
max-ships-per-player: 5
|
||||||
|
|
||||||
|
# Taille du spaceship a la creation (level 1)
|
||||||
|
default-size:
|
||||||
|
x: 4
|
||||||
|
y: 4
|
||||||
|
z: 6
|
||||||
|
|
||||||
|
# Taille maximale (atteinte avec les niveaux)
|
||||||
|
max-size:
|
||||||
|
x: 16
|
||||||
|
y: 8
|
||||||
|
z: 16
|
||||||
|
|
||||||
|
# Couts en money (Vault / EssentialsEconomy)
|
||||||
|
costs:
|
||||||
|
summon: 100 # cout pour charger un spaceship
|
||||||
|
unload: 0 # cout pour decharger
|
||||||
|
upgrade-base: 1000 # cout d'amelioration : upgrade-base * niveau actuel
|
||||||
|
space-jump-per-block: 1 # cout par bloc lors d'un saut spatial
|
||||||
|
|
||||||
|
# Saut spatial
|
||||||
|
space-jump:
|
||||||
|
blocks-per-level: 100 # max blocs autorises = blocks-per-level * niveau
|
||||||
|
min-blocks: 1
|
||||||
|
max-blocks-hard-cap: 5000
|
||||||
|
|
||||||
|
# Hauteur a laquelle le spaceship apparait au-dessus du joueur quand il est invoque
|
||||||
|
summon-offset-y: 30
|
||||||
|
|
||||||
|
# Materiau utilise pour la plateforme par defaut lors de la creation d'un nouveau spaceship
|
||||||
|
default-platform-material: SMOOTH_QUARTZ
|
||||||
|
default-edge-material: QUARTZ_PILLAR
|
||||||
|
default-glass-material: WHITE_STAINED_GLASS
|
||||||
|
|
||||||
|
# Nom par defaut des nouveaux spaceships (%n% = numero)
|
||||||
|
default-ship-name: "Spaceship #%n%"
|
||||||
|
|
||||||
|
# Verifications de securite
|
||||||
|
safety:
|
||||||
|
# Empecher l'invocation si des blocs solides sont presents a l'emplacement
|
||||||
|
check-collision: true
|
||||||
|
# Empecher la destruction du sol (le spaceship doit etre dans le ciel)
|
||||||
|
min-y-above-player: 10
|
||||||
|
|
||||||
|
# Messages (couleurs Minecraft, &x)
|
||||||
|
messages:
|
||||||
|
prefix: "&8[&bSpaceShip&8] &7"
|
||||||
|
no-permission: "&cVous n'avez pas la permission !"
|
||||||
|
not-owner: "&cCe spaceship ne vous appartient pas."
|
||||||
|
ship-given: "&aTelecommande du spaceship &b%name% &acreee et donnee."
|
||||||
|
ship-summoned: "&aSpaceship &b%name% &acharge dans le ciel !"
|
||||||
|
ship-unloaded: "&aSpaceship &b%name% &adecharge, modifications sauvegardees."
|
||||||
|
ship-teleport-up: "&aTeleportation sur le spaceship &b%name%&a..."
|
||||||
|
ship-teleport-down: "&aRedescente sur la terre ferme..."
|
||||||
|
ship-already-loaded: "&eVotre spaceship est deja charge."
|
||||||
|
ship-not-loaded: "&eVotre spaceship n'est pas charge."
|
||||||
|
ship-collision: "&cImpossible d'invoquer le spaceship ici : zone obstruee."
|
||||||
|
max-ships-reached: "&cVous avez atteint la limite de spaceships (%max%)."
|
||||||
|
ship-deleted: "&cSpaceship supprime."
|
||||||
|
not-enough-money: "&cFonds insuffisants : %need% requis, %have% disponible."
|
||||||
|
vault-unavailable: "&cVault/EssentialsEconomy est indisponible, action impossible."
|
||||||
|
space-jump-success: "&aSaut spatial : %dist% blocs vers %dir% (-%cost%)."
|
||||||
|
space-jump-not-on-ship: "&cVous devez etre sur le spaceship pour effectuer un saut."
|
||||||
|
space-jump-too-far: "&cDistance trop grande (max %max% au niveau %lvl%)."
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: SpaceShipProject
|
||||||
|
main: com.spaceshipproject.SpaceShipProject
|
||||||
|
version: 1.0
|
||||||
|
api-version: 1.21
|
||||||
|
author: CreatorOfNothing
|
||||||
|
description: Spaceships personnels charges en plein ciel, telecommande boussole.
|
||||||
|
softdepend: [Vault, EssentialsX]
|
||||||
|
|
||||||
|
commands:
|
||||||
|
spaceship:
|
||||||
|
description: Commandes joueur du plugin SpaceShipProject
|
||||||
|
usage: /spaceship <create|give|info|list|delete|remote|reload|help>
|
||||||
|
aliases: [ss, ship]
|
||||||
|
permission: spaceship.use
|
||||||
|
spaceshipadmin:
|
||||||
|
description: Commandes d'administration du plugin SpaceShipProject
|
||||||
|
usage: /spaceshipadmin <reload|list|delete|addmoney|setlevel|setname|setconfig|getconfig|loadmodel|savemodel|cancelmodel|help>
|
||||||
|
aliases: [ssa, shipadmin]
|
||||||
|
permission: spaceship.admin
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
spaceship.*:
|
||||||
|
description: Toutes les permissions de SpaceShipProject
|
||||||
|
children:
|
||||||
|
spaceship.use: true
|
||||||
|
spaceship.create: true
|
||||||
|
spaceship.give: true
|
||||||
|
spaceship.info.other: true
|
||||||
|
spaceship.delete: true
|
||||||
|
spaceship.reload: true
|
||||||
|
spaceship.admin: true
|
||||||
|
default: op
|
||||||
|
spaceship.use:
|
||||||
|
description: Utiliser sa propre telecommande
|
||||||
|
default: true
|
||||||
|
spaceship.create:
|
||||||
|
description: Creer un nouveau spaceship (/ss create)
|
||||||
|
default: true
|
||||||
|
spaceship.give:
|
||||||
|
description: Recuperer une telecommande perdue (la sienne ou celle d'un autre avec admin)
|
||||||
|
default: true
|
||||||
|
spaceship.info.other:
|
||||||
|
description: Voir les infos detaillees d'un autre joueur
|
||||||
|
default: op
|
||||||
|
spaceship.delete:
|
||||||
|
description: Supprimer un de ses spaceships
|
||||||
|
default: true
|
||||||
|
spaceship.reload:
|
||||||
|
description: Recharger la configuration
|
||||||
|
default: op
|
||||||
|
spaceship.admin:
|
||||||
|
description: Permissions d'administration (bypass de propriete, /ssa, etc.)
|
||||||
|
default: op
|
||||||
|
spaceship.bypass.cost:
|
||||||
|
description: Permet de ne pas payer les couts d'invocation / saut / upgrade
|
||||||
|
default: op
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
# =========================================================
|
||||||
|
# SpaceShipProject - Configuration
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# Limite de spaceships par joueur (0 = illimite)
|
||||||
|
max-ships-per-player: 5
|
||||||
|
|
||||||
|
# Taille du spaceship a la creation (level 1)
|
||||||
|
default-size:
|
||||||
|
x: 4
|
||||||
|
y: 4
|
||||||
|
z: 6
|
||||||
|
|
||||||
|
# Taille maximale (atteinte avec les niveaux)
|
||||||
|
max-size:
|
||||||
|
x: 16
|
||||||
|
y: 8
|
||||||
|
z: 16
|
||||||
|
|
||||||
|
# Couts en money (Vault / EssentialsEconomy)
|
||||||
|
costs:
|
||||||
|
summon: 100 # cout pour charger un spaceship
|
||||||
|
unload: 0 # cout pour decharger
|
||||||
|
upgrade-base: 1000 # cout d'amelioration : upgrade-base * niveau actuel
|
||||||
|
space-jump-per-block: 1 # cout par bloc lors d'un saut spatial
|
||||||
|
|
||||||
|
# Saut spatial
|
||||||
|
space-jump:
|
||||||
|
blocks-per-level: 100 # max blocs autorises = blocks-per-level * niveau
|
||||||
|
min-blocks: 1
|
||||||
|
max-blocks-hard-cap: 5000
|
||||||
|
|
||||||
|
# Hauteur a laquelle le spaceship apparait au-dessus du joueur quand il est invoque
|
||||||
|
summon-offset-y: 30
|
||||||
|
|
||||||
|
# Materiau utilise pour la plateforme par defaut lors de la creation d'un nouveau spaceship
|
||||||
|
default-platform-material: SMOOTH_QUARTZ
|
||||||
|
default-edge-material: QUARTZ_PILLAR
|
||||||
|
default-glass-material: WHITE_STAINED_GLASS
|
||||||
|
|
||||||
|
# Nom par defaut des nouveaux spaceships (%n% = numero)
|
||||||
|
default-ship-name: "Spaceship #%n%"
|
||||||
|
|
||||||
|
# Verifications de securite
|
||||||
|
safety:
|
||||||
|
# Empecher l'invocation si des blocs solides sont presents a l'emplacement
|
||||||
|
check-collision: true
|
||||||
|
# Empecher la destruction du sol (le spaceship doit etre dans le ciel)
|
||||||
|
min-y-above-player: 10
|
||||||
|
|
||||||
|
# Messages (couleurs Minecraft, &x)
|
||||||
|
messages:
|
||||||
|
prefix: "&8[&bSpaceShip&8] &7"
|
||||||
|
no-permission: "&cVous n'avez pas la permission !"
|
||||||
|
not-owner: "&cCe spaceship ne vous appartient pas."
|
||||||
|
ship-given: "&aTelecommande du spaceship &b%name% &acreee et donnee."
|
||||||
|
ship-summoned: "&aSpaceship &b%name% &acharge dans le ciel !"
|
||||||
|
ship-unloaded: "&aSpaceship &b%name% &adecharge, modifications sauvegardees."
|
||||||
|
ship-teleport-up: "&aTeleportation sur le spaceship &b%name%&a..."
|
||||||
|
ship-teleport-down: "&aRedescente sur la terre ferme..."
|
||||||
|
ship-already-loaded: "&eVotre spaceship est deja charge."
|
||||||
|
ship-not-loaded: "&eVotre spaceship n'est pas charge."
|
||||||
|
ship-collision: "&cImpossible d'invoquer le spaceship ici : zone obstruee."
|
||||||
|
max-ships-reached: "&cVous avez atteint la limite de spaceships (%max%)."
|
||||||
|
ship-deleted: "&cSpaceship supprime."
|
||||||
|
not-enough-money: "&cFonds insuffisants : %need% requis, %have% disponible."
|
||||||
|
vault-unavailable: "&cVault/EssentialsEconomy est indisponible, action impossible."
|
||||||
|
space-jump-success: "&aSaut spatial : %dist% blocs vers %dir% (-%cost%)."
|
||||||
|
space-jump-not-on-ship: "&cVous devez etre sur le spaceship pour effectuer un saut."
|
||||||
|
space-jump-too-far: "&cDistance trop grande (max %max% au niveau %lvl%)."
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: SpaceShipProject
|
||||||
|
main: com.spaceshipproject.SpaceShipProject
|
||||||
|
version: 1.0
|
||||||
|
api-version: 1.21
|
||||||
|
author: CreatorOfNothing
|
||||||
|
description: Spaceships personnels charges en plein ciel, telecommande boussole.
|
||||||
|
softdepend: [Vault, EssentialsX]
|
||||||
|
|
||||||
|
commands:
|
||||||
|
spaceship:
|
||||||
|
description: Commandes joueur du plugin SpaceShipProject
|
||||||
|
usage: /spaceship <create|give|info|list|delete|remote|reload|help>
|
||||||
|
aliases: [ss, ship]
|
||||||
|
permission: spaceship.use
|
||||||
|
spaceshipadmin:
|
||||||
|
description: Commandes d'administration du plugin SpaceShipProject
|
||||||
|
usage: /spaceshipadmin <reload|list|delete|addmoney|setlevel|setname|setconfig|getconfig|loadmodel|savemodel|cancelmodel|help>
|
||||||
|
aliases: [ssa, shipadmin]
|
||||||
|
permission: spaceship.admin
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
spaceship.*:
|
||||||
|
description: Toutes les permissions de SpaceShipProject
|
||||||
|
children:
|
||||||
|
spaceship.use: true
|
||||||
|
spaceship.create: true
|
||||||
|
spaceship.give: true
|
||||||
|
spaceship.info.other: true
|
||||||
|
spaceship.delete: true
|
||||||
|
spaceship.reload: true
|
||||||
|
spaceship.admin: true
|
||||||
|
default: op
|
||||||
|
spaceship.use:
|
||||||
|
description: Utiliser sa propre telecommande
|
||||||
|
default: true
|
||||||
|
spaceship.create:
|
||||||
|
description: Creer un nouveau spaceship (/ss create)
|
||||||
|
default: true
|
||||||
|
spaceship.give:
|
||||||
|
description: Recuperer une telecommande perdue (la sienne ou celle d'un autre avec admin)
|
||||||
|
default: true
|
||||||
|
spaceship.info.other:
|
||||||
|
description: Voir les infos detaillees d'un autre joueur
|
||||||
|
default: op
|
||||||
|
spaceship.delete:
|
||||||
|
description: Supprimer un de ses spaceships
|
||||||
|
default: true
|
||||||
|
spaceship.reload:
|
||||||
|
description: Recharger la configuration
|
||||||
|
default: op
|
||||||
|
spaceship.admin:
|
||||||
|
description: Permissions d'administration (bypass de propriete, /ssa, etc.)
|
||||||
|
default: op
|
||||||
|
spaceship.bypass.cost:
|
||||||
|
description: Permet de ne pas payer les couts d'invocation / saut / upgrade
|
||||||
|
default: op
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
artifactId=SpaceShipProject
|
||||||
|
groupId=com.spaceshipproject
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
com/spaceshipproject/RemoteManager.class
|
||||||
|
com/spaceshipproject/EconomyHook.class
|
||||||
|
com/spaceshipproject/SpaceShipProject.class
|
||||||
|
com/spaceshipproject/SpaceshipManager.class
|
||||||
|
com/spaceshipproject/BlockProtectionListener.class
|
||||||
|
com/spaceshipproject/ModelManager.class
|
||||||
|
com/spaceshipproject/ShipSizing.class
|
||||||
|
com/spaceshipproject/PlayerListener.class
|
||||||
|
com/spaceshipproject/Spaceship.class
|
||||||
|
com/spaceshipproject/RemoteListener.class
|
||||||
|
com/spaceshipproject/DatabaseManager.class
|
||||||
|
com/spaceshipproject/ConfigManager.class
|
||||||
|
com/spaceshipproject/LoadedSpaceship.class
|
||||||
|
com/spaceshipproject/SpaceShipAdminCommand.class
|
||||||
|
com/spaceshipproject/SpaceShipCommand.class
|
||||||
|
com/spaceshipproject/SchematicHelper.class
|
||||||
|
com/spaceshipproject/SpaceshipGUI.class
|
||||||
|
com/spaceshipproject/SpaceshipGUIHolder.class
|
||||||
|
com/spaceshipproject/SpaceshipManager$LoadedShipOwnerInfo.class
|
||||||
|
com/spaceshipproject/SpaceJumpGUI.class
|
||||||
|
com/spaceshipproject/ModelManager$ModelEditSession.class
|
||||||
|
com/spaceshipproject/SpaceshipGUIListener.class
|
||||||
|
com/spaceshipproject/SpaceshipGuiPanel.class
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/ConfigManager.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SchematicHelper.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/ModelManager.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/PlayerListener.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceshipManager.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/Spaceship.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceshipGUIListener.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/EconomyHook.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceJumpGUI.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceShipProject.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceshipGUI.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceshipGUIHolder.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/ShipSizing.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceshipGuiPanel.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/RemoteManager.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceShipCommand.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/RemoteListener.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/DatabaseManager.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/SpaceShipAdminCommand.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/BlockProtectionListener.java
|
||||||
|
/home/bat/github/mcplugin/SpaceShipProject/src/main/java/com/spaceshipproject/LoadedSpaceship.java
|
||||||
Binary file not shown.
Reference in New Issue
Block a user