This commit is contained in:
gpatruno
2026-05-19 15:35:55 +02:00
commit 94404156d9
55 changed files with 4518 additions and 0 deletions
+393
View File
@@ -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.
+83
View File
@@ -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); }
}
+70
View File
@@ -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%)."
+56
View File
@@ -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.
+70
View File
@@ -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%)."
+56
View File
@@ -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
+3
View File
@@ -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.