diff --git a/src/main/java/fr/openmc/api/datapacks/DatapackInjector.java b/src/main/java/fr/openmc/api/datapacks/DatapackInjector.java new file mode 100644 index 000000000..c5fe96d7b --- /dev/null +++ b/src/main/java/fr/openmc/api/datapacks/DatapackInjector.java @@ -0,0 +1,12 @@ +package fr.openmc.api.datapacks; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; + +public interface DatapackInjector { + Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + void inject(File rootFile); +} diff --git a/src/main/java/fr/openmc/api/datapacks/OMCDatapack.java b/src/main/java/fr/openmc/api/datapacks/OMCDatapack.java new file mode 100644 index 000000000..2352a1b9b --- /dev/null +++ b/src/main/java/fr/openmc/api/datapacks/OMCDatapack.java @@ -0,0 +1,56 @@ +package fr.openmc.api.datapacks; + +import fr.openmc.api.datapacks.injectors.PackMetadataInjector; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import lombok.Getter; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +@Getter +@SuppressWarnings("UnstableApiUsage") +public class OMCDatapack { + private final String packName; + private final String namespace; + private final Set injectors = new HashSet<>(); + + public OMCDatapack(String packName, String namespace) { + this.packName = packName; + this.namespace = namespace; + } + + public void build(BootstrapContext context) throws IOException { + Path tempDir = Files.createTempDirectory("datapacks-openmc"); + + runInjector(tempDir, new PackMetadataInjector()); + + for (DatapackInjector injector : injectors) { + runInjector(tempDir, injector); + } + + context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY.newHandler( + event -> { + try { + URI uri = tempDir.toUri(); + + event.registrar().discoverPack(uri, "openmc-injected"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + )); + } + + public void addInjector(DatapackInjector injector) { + injectors.add(injector); + } + + private static void runInjector(Path datapackRoot, DatapackInjector injector) { + injector.inject(datapackRoot.toFile()); + } +} diff --git a/src/main/java/fr/openmc/api/datapacks/injectors/DimensionTypesInjector.java b/src/main/java/fr/openmc/api/datapacks/injectors/DimensionTypesInjector.java new file mode 100644 index 000000000..39a768ae5 --- /dev/null +++ b/src/main/java/fr/openmc/api/datapacks/injectors/DimensionTypesInjector.java @@ -0,0 +1,269 @@ +package fr.openmc.api.datapacks.injectors; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import fr.openmc.api.datapacks.DatapackInjector; +import net.minecraft.world.level.dimension.DimensionType; +import org.bukkit.Particle; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Classe qui représente les données trouvable dans un dimension type + * Qui injecte directement cela sous forme .json dans le datapack + */ +public class DimensionTypesInjector implements DatapackInjector { + + private final String namespace; + private final Map entries = new LinkedHashMap<>(); + + public DimensionTypesInjector(String namespace) { + this.namespace = namespace; + } + + public DimensionTypesInjector add(String id, Consumer builder) { + DimensionTypeBuilder instance = new DimensionTypeBuilder(); + builder.accept(instance); + entries.put(id, instance); + return this; + } + + public DimensionTypesInjector add(String id, DimensionTypeBuilder builder) { + entries.put(id, builder); + return this; + } + + @Override + public void inject(File rootFile) { + if (entries.isEmpty()) return; + + Path root = rootFile.toPath().resolve("data").resolve(namespace).resolve("dimension_type"); + try { + Files.createDirectories(root); + for (var entry : entries.entrySet()) { + Path dimensionTypeFile = root.resolve(entry.getKey() + ".json"); + Files.writeString(dimensionTypeFile, GSON.toJson(entry.getValue().toJson())); + } + } catch (IOException e) { + throw new IllegalStateException("Cannot write dimension_type files", e); + } + } + + /** + * Exemple simple d'un dimension type : + * { + * "attributes": {}, + * "ambient_light": 0, + * "coordinate_scale": 1, + * "default_clock": "minecraft:overworld", + * "has_ceiling": false, + * "has_ender_dragon_fight": false, + * "has_skylight": true, + * "has_fixed_time": true, + * "skybox": "overworld", + * "cardinal_light": "default", + * "height": 384, + * "infiniburn": "#minecraft:infiniburn_overworld", + * "logical_height": 384, + * "min_y": -64, + * "monster_spawn_block_light_limit": 0, + * "monster_spawn_light_level": { + * "type": "minecraft:uniform", + * "max_inclusive": 7, + * "min_inclusive": 0 + * }, + * "timelines": "#minecraft:in_overworld" + * } + */ + public static final class DimensionTypeBuilder { + private JsonObject attributes; + private Double ambientLight = 0.0; + private Double coordinateScale = 1.0; + private String defaultClock = "overworld"; + private Boolean hasCeiling = false; + private Boolean hasEnderDragonFlight = false; + private Boolean hasSkylight = true; + private Boolean hasFixedTime = false; + private String skybox = "overworld"; + private String cardinalLight = "default"; + private Integer height = 384; + private String infiniburn = "#infiniburn_overworld"; + private Integer logicalHeight = 384; + private Integer minY = -64; + private Integer monsterSpawnBlockLightLimit = 0; + private JsonElement monsterSpawnLightLevel = new JsonPrimitive(0); + private String timelines = "#minecraft:in_overworld"; + + public DimensionTypeBuilder attributes(Consumer builder) { + JsonObject obj = new JsonObject(); + builder.accept(obj); + this.attributes = obj; + return this; + } + + public DimensionTypeBuilder attributes(JsonObject attributes) { + this.attributes = attributes; + return this; + } + + /** + * Ajoute un attribut "minecraft:visual/ambient_particles" simple. + * Exemple : + * "minecraft:visual/ambient_particles": [ { "particle": { "type": "minecraft:crimson_spore" }, "probability": 0.025 } ] + */ + public DimensionTypeBuilder ambientParticles(String particleType, double probability) { + if (this.attributes == null) this.attributes = new JsonObject(); + JsonObject entry = new JsonObject(); + JsonObject particle = new JsonObject(); + particle.addProperty("type", particleType); + entry.add("particle", particle); + entry.addProperty("probability", probability); + + if (this.attributes.has("minecraft:visual/ambient_particles") && this.attributes.get("minecraft:visual/ambient_particles").isJsonArray()) { + this.attributes.getAsJsonArray("minecraft:visual/ambient_particles").add(entry); + } else { + JsonArray arr = new JsonArray(); + arr.add(entry); + this.attributes.add("minecraft:visual/ambient_particles", arr); + } + return this; + } + + /** + * Ajoute un attribut "minecraft:visual/ambient_particles" simple. + * Exemple : + * "minecraft:visual/ambient_particles": [ { "particle": { "type": "minecraft:crimson_spore" }, "probability": 0.025 } ] + */ + public DimensionTypeBuilder ambientParticles(Particle particle, double probability) { + return ambientParticles(particle.getKey().toString(), probability); + } + + public DimensionTypeBuilder ambientLight(double ambientLight) { + this.ambientLight = ambientLight; + return this; + } + + public DimensionTypeBuilder coordinateScale(double coordinateScale) { + this.coordinateScale = coordinateScale; + return this; + } + + public DimensionTypeBuilder defaultClock(String keyOfClock) { + this.defaultClock = keyOfClock; + return this; + } + + public DimensionTypeBuilder hasCeiling(boolean hasCeiling) { + this.hasCeiling = hasCeiling; + return this; + } + + public DimensionTypeBuilder hasEnderDragonFlight(boolean hasEnderDragonFlight) { + this.hasEnderDragonFlight = hasEnderDragonFlight; + return this; + } + + public DimensionTypeBuilder hasSkylight(boolean hasSkylight) { + this.hasSkylight = hasSkylight; + return this; + } + + public DimensionTypeBuilder hasFixedTime(boolean hasFixedTime) { + this.hasFixedTime = hasFixedTime; + return this; + } + + public DimensionTypeBuilder skybox(String skybox) { + this.skybox = skybox; + return this; + } + + public DimensionTypeBuilder skybox(DimensionType.Skybox skybox) { + return skybox(skybox.getSerializedName()); + } + + public DimensionTypeBuilder cardinalLight(String cardinalLight) { + this.cardinalLight = cardinalLight; + return this; + } + + public DimensionTypeBuilder height(int height) { + this.height = height; + return this; + } + + public DimensionTypeBuilder infiniburn(String infiniburn) { + this.infiniburn = infiniburn; + return this; + } + + public DimensionTypeBuilder logicalHeight(int logicalHeight) { + this.logicalHeight = logicalHeight; + return this; + } + + public DimensionTypeBuilder minY(int minY) { + this.minY = minY; + return this; + } + + public DimensionTypeBuilder monsterSpawnBlockLightLimit(int limit) { + this.monsterSpawnBlockLightLimit = limit; + return this; + } + + public DimensionTypeBuilder monsterSpawnLightLevelUniform(int minInclusive, int maxInclusive) { + JsonObject uniform = new JsonObject(); + uniform.addProperty("type", "minecraft:uniform"); + uniform.addProperty("min_inclusive", minInclusive); + uniform.addProperty("max_inclusive", maxInclusive); + this.monsterSpawnLightLevel = uniform; + return this; + } + + public DimensionTypeBuilder monsterSpawnLightLevel(int level) { + this.monsterSpawnLightLevel = new JsonPrimitive(level); + return this; + } + + public DimensionTypeBuilder monsterSpawnLightLevel(JsonElement monsterSpawnLightLevel) { + this.monsterSpawnLightLevel = monsterSpawnLightLevel; + return this; + } + + public DimensionTypeBuilder timelines(String timelines) { + this.timelines = timelines; + return this; + } + + private JsonObject toJson() { + JsonObject json = new JsonObject(); + if (attributes != null) json.add("attributes", attributes); + if (ambientLight != null) json.addProperty("ambient_light", ambientLight); + if (coordinateScale != null) json.addProperty("coordinate_scale", coordinateScale); + if (defaultClock != null) json.addProperty("default_clock", defaultClock); + if (hasCeiling != null) json.addProperty("has_ceiling", hasCeiling); + if (hasEnderDragonFlight != null) json.addProperty("has_ender_dragon_fight", hasEnderDragonFlight); + if (hasSkylight != null) json.addProperty("has_skylight", hasSkylight); + if (hasFixedTime != null) json.addProperty("has_fixed_time", hasFixedTime); + if (skybox != null) json.addProperty("skybox", skybox); + if (cardinalLight != null) json.addProperty("cardinal_light", cardinalLight); + if (height != null) json.addProperty("height", height); + if (infiniburn != null) json.addProperty("infiniburn", infiniburn); + if (logicalHeight != null) json.addProperty("logical_height", logicalHeight); + if (minY != null) json.addProperty("min_y", minY); + if (monsterSpawnBlockLightLimit != null) json.addProperty("monster_spawn_block_light_limit", monsterSpawnBlockLightLimit); + if (monsterSpawnLightLevel != null) json.add("monster_spawn_light_level", monsterSpawnLightLevel); + if (timelines != null) json.addProperty("timelines", timelines); + return json; + } + } +} diff --git a/src/main/java/fr/openmc/api/datapacks/injectors/PackMetadataInjector.java b/src/main/java/fr/openmc/api/datapacks/injectors/PackMetadataInjector.java new file mode 100644 index 000000000..ee06d15b2 --- /dev/null +++ b/src/main/java/fr/openmc/api/datapacks/injectors/PackMetadataInjector.java @@ -0,0 +1,36 @@ +package fr.openmc.api.datapacks.injectors; + +import fr.openmc.api.datapacks.DatapackInjector; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PackMetadataInjector implements DatapackInjector { + private static final double[] PACK_FORMAT = new double[] {101.1, 1}; + + @Override + public void inject(File rootFile) { + Path root = rootFile.toPath(); + try { + Path metaDataFile = root.resolve("pack.mcmeta"); + Files.writeString(metaDataFile, packMcMeta()); + } catch (IOException e) { + throw new IllegalStateException("Cannot write pack mcmeta file", e); + } + } + + private String packMcMeta() { + return String.format(""" + { + "pack": { + "description": "OMC datapack injected from plugin", + "pack_format": %s, + "min_format": [%s, %s], + "max_format": [%s, %s] + } + } + """, PACK_FORMAT[0], PACK_FORMAT[0], PACK_FORMAT[1], PACK_FORMAT[0], PACK_FORMAT[1]); + } +} diff --git a/src/main/java/fr/openmc/core/CommandsManager.java b/src/main/java/fr/openmc/core/CommandsManager.java index 6defe4096..45ffc83d1 100644 --- a/src/main/java/fr/openmc/core/CommandsManager.java +++ b/src/main/java/fr/openmc/core/CommandsManager.java @@ -10,6 +10,7 @@ import fr.openmc.core.commands.utils.Restart; import fr.openmc.core.commands.utils.Socials; import fr.openmc.core.features.credits.CreditsCommand; +import fr.openmc.core.registry.ambient.commands.CustomAmbientCommands; import lombok.Getter; import revxrsal.commands.Lamp; import revxrsal.commands.bukkit.BukkitLamp; @@ -46,6 +47,7 @@ private static void registerCommands() { new Restart(), new CreditsCommand(), new CustomItemCommand(), + new CustomAmbientCommands(), new ToastCommand() ); } diff --git a/src/main/java/fr/openmc/core/ListenersManager.java b/src/main/java/fr/openmc/core/ListenersManager.java index 85fbe0d62..5f71dc22c 100644 --- a/src/main/java/fr/openmc/core/ListenersManager.java +++ b/src/main/java/fr/openmc/core/ListenersManager.java @@ -5,6 +5,7 @@ import fr.openmc.core.features.itemsadder.SpawnerExtractorListener; import fr.openmc.core.hooks.itemsadder.ItemsAdderHook; import fr.openmc.core.listeners.*; +import fr.openmc.core.registry.ambient.listeners.CustomAmbientListener; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.event.Listener; @@ -33,7 +34,8 @@ public static void init() { new EquipableItemListener(), new NoMoreRabbit(), new ArmorListener(), - new BlockBreakListener() + new BlockBreakListener(), + new CustomAmbientListener() ); if (!OMCPlugin.isUnitTestVersion()) { diff --git a/src/main/java/fr/openmc/core/OMCRegistry.java b/src/main/java/fr/openmc/core/OMCRegistry.java index 6569bebc7..5d29c6e7d 100644 --- a/src/main/java/fr/openmc/core/OMCRegistry.java +++ b/src/main/java/fr/openmc/core/OMCRegistry.java @@ -2,12 +2,14 @@ import fr.openmc.core.bootstrap.integration.OMCLogger; import fr.openmc.core.bootstrap.registries.LifecycleRegistry; +import fr.openmc.core.registry.ambient.CustomAmbientRegistry; import fr.openmc.core.registry.enchantments.CustomEnchantmentRegistry; import fr.openmc.core.registry.items.CustomItemRegistry; import fr.openmc.core.registry.loottable.CustomLootTableRegistry; import fr.openmc.core.registry.mobs.CustomMobRegistry; import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import java.io.IOException; import java.util.List; @SuppressWarnings("UnstableApiUsage") @@ -17,12 +19,14 @@ public final class OMCRegistry { public static final CustomMobRegistry CUSTOM_MOBS = new CustomMobRegistry(); public static final CustomEnchantmentRegistry CUSTOM_ENCHANTS = new CustomEnchantmentRegistry(); public static final CustomLootTableRegistry CUSTOM_LOOT_TABLES = new CustomLootTableRegistry(); + public static final CustomAmbientRegistry CUSTOM_AMBIENTS = new CustomAmbientRegistry(); private static final List ALL = List.of( CUSTOM_ITEMS, CUSTOM_MOBS, CUSTOM_ENCHANTS, - CUSTOM_LOOT_TABLES + CUSTOM_LOOT_TABLES, + CUSTOM_AMBIENTS ); private OMCRegistry() {} @@ -30,7 +34,12 @@ private OMCRegistry() {} public static void bootstrapAll(BootstrapContext context) { for (LifecycleRegistry r : OMCRegistry.ALL) { if (isOverridden(r, "bootstrap", BootstrapContext.class)) { - r.bootstrap(context); + try { + r.bootstrap(context); + } catch (IOException e) { + OMCLogger.errorFormatted("Erreur lors du chargement du registre '{}' lors du bootstrap", r.getClass().getSimpleName()); + OMCLogger.error(e.getMessage()); + } OMCLogger.successFormatted("Registre {} chargé pendant le bootstrap", r.getClass().getSimpleName()); } } diff --git a/src/main/java/fr/openmc/core/bootstrap/registries/KeyedRegistry.java b/src/main/java/fr/openmc/core/bootstrap/registries/KeyedRegistry.java new file mode 100644 index 000000000..0988b9ad7 --- /dev/null +++ b/src/main/java/fr/openmc/core/bootstrap/registries/KeyedRegistry.java @@ -0,0 +1,23 @@ +package fr.openmc.core.bootstrap.registries; + +public interface KeyedRegistry { + K key(V registryObject); + + void register(K key, V value); + + default void register(V value) { + register(key(value), value); + } + + default void register(V... values) { + for (V value : values) { + register(value); + } + } + + default void register(Iterable values) { + for (V value : values) { + register(value); + } + } +} diff --git a/src/main/java/fr/openmc/core/bootstrap/registries/LifecycleRegistry.java b/src/main/java/fr/openmc/core/bootstrap/registries/LifecycleRegistry.java index 51990346f..541e7a503 100644 --- a/src/main/java/fr/openmc/core/bootstrap/registries/LifecycleRegistry.java +++ b/src/main/java/fr/openmc/core/bootstrap/registries/LifecycleRegistry.java @@ -2,9 +2,11 @@ import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import java.io.IOException; + @SuppressWarnings("UnstableApiUsage") public interface LifecycleRegistry { - default void bootstrap(BootstrapContext context) {} + default void bootstrap(BootstrapContext context) throws IOException {} default void init() {} diff --git a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java new file mode 100644 index 000000000..19e475510 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java @@ -0,0 +1,133 @@ +package fr.openmc.core.registry.ambient; + +import fr.openmc.api.datapacks.DatapackInjector; +import fr.openmc.api.datapacks.injectors.DimensionTypesInjector; +import fr.openmc.core.utils.nms.PlayerRespawnNMS; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public abstract class CustomAmbient { + // ** UUID playerUUID -> String idAmbient + public static final Map ACTIVE_AMBIENTS = new HashMap<>(); + private Holder CACHED_DIMENSION_TYPE = null; + + public abstract String getId(); + public abstract DimensionTypesInjector.DimensionTypeBuilder getDimensionTypeBuilder(); + + /** + * Choix de la transition de dimension lorsque le joueur change d'ambience + * @return La key de la dimension + */ + public abstract ResourceKey getTransitionDimension(); + + /** + * Converti notre DimensionTypeBuild en un injecteur de datapack + * et qui mettera les dimension_type sous le namepsace omc_ambient + * @return Un datapack injector + */ + public DatapackInjector toDimensionTypeInjector() { + return new DimensionTypesInjector("omc_ambient").add(getId(), getDimensionTypeBuilder()); + } + + /** + * Applique l'ambience sur un Joueur + * @param player Le joueur concerné + */ + public void apply(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + + // * On envoie le packet respawn qui applique l'ambience + PlayerRespawnNMS.sendPacket( + nmsPlayer, + getPlayerAmbientSpawnInfo(nmsPlayer), + getTransitionDimensionForPlayer(nmsPlayer) + ); + + ACTIVE_AMBIENTS.put(player.getUniqueId(), this.getId()); + } + + /** + * Retire l'ambience du Joueur + * @param player le joueur ciblé + */ + public void reset(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + + // * On envoie le packet respawn qui remets tout a la normale + PlayerRespawnNMS.sendPacket( + nmsPlayer, + nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()), + getTransitionDimensionForPlayer(nmsPlayer) + ); + + ACTIVE_AMBIENTS.remove(player.getUniqueId()); + } + + /** + * Calcule la dimension de transition appropriée pour le joueur + * Si le joueur est en OVERWORLD, on transitionne vers l'ambience + * Sinon on revient à l'OVERWORLD + * @param nmsPlayer le joueur ciblé + * @return la key de dimension + */ + private ResourceKey getTransitionDimensionForPlayer(ServerPlayer nmsPlayer) { + return nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()).dimension().equals(Level.OVERWORLD) + ? this.getTransitionDimension() + : Level.OVERWORLD; + } + + /** + * Crée les informations de spawn du joueur en fonction de l'ambience ciblé + * En gros on cherche le dimension_type enregistré dans le registre, et on le mets dans les infos de spawn du joueur + * @param nmsPlayer le joueur ciblé + * @return les informations de spawn + */ + private CommonPlayerSpawnInfo getPlayerAmbientSpawnInfo(ServerPlayer nmsPlayer) { + CommonPlayerSpawnInfo spawnInfo = nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()); + + return new CommonPlayerSpawnInfo( + getDimensionType(), + spawnInfo.dimension(), + spawnInfo.seed(), + spawnInfo.gameType(), + spawnInfo.previousGameType(), + spawnInfo.isDebug(), + spawnInfo.isFlat(), + spawnInfo.lastDeathLocation(), + spawnInfo.portalCooldown(), + spawnInfo.seaLevel() + ); + } + + private Holder getDimensionType() { + if (CACHED_DIMENSION_TYPE != null) + return CACHED_DIMENSION_TYPE; + + ResourceKey key = ResourceKey.create( + Registries.DIMENSION_TYPE, + Identifier.fromNamespaceAndPath("omc_ambient", this.getId()) + ); + + Registry dimRegistry = + MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE); + + CACHED_DIMENSION_TYPE = dimRegistry.get(key).orElseThrow(() -> + new IllegalStateException("DimensionType omc_ambient:"+ this.getId() +" introuvable") + ); + return CACHED_DIMENSION_TYPE; + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java new file mode 100644 index 000000000..dd29def36 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java @@ -0,0 +1,34 @@ +package fr.openmc.core.registry.ambient; + +import fr.openmc.api.datapacks.OMCDatapack; +import fr.openmc.core.bootstrap.registries.KeyedRegistry; +import fr.openmc.core.bootstrap.registries.Registry; +import fr.openmc.core.registry.ambient.contents.DarkAmbient; +import fr.openmc.core.registry.ambient.contents.HellAmbient; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; + +import java.io.IOException; + +@SuppressWarnings("UnstableApiUsage") +public class CustomAmbientRegistry extends Registry implements KeyedRegistry { + private final OMCDatapack ambientDatapack = new OMCDatapack("openmc", "omc_ambient"); + + @Override + public String key(CustomAmbient registryObject) { + return registryObject.getId(); + } + + @Override + public void bootstrap(BootstrapContext context) throws IOException { + register( + new DarkAmbient(), + new HellAmbient() + ); + + for (CustomAmbient ambient : values()) { + ambientDatapack.addInjector(ambient.toDimensionTypeInjector()); + } + + ambientDatapack.build(context); + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/commands/CustomAmbientCommands.java b/src/main/java/fr/openmc/core/registry/ambient/commands/CustomAmbientCommands.java new file mode 100644 index 000000000..71c919445 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/commands/CustomAmbientCommands.java @@ -0,0 +1,58 @@ +package fr.openmc.core.registry.ambient.commands; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.ambient.CustomAmbient; +import fr.openmc.core.registry.ambient.commands.autocomplete.CustomAmbientAutoComplete; +import fr.openmc.core.utils.text.messages.MessageType; +import fr.openmc.core.utils.text.messages.MessagesManager; +import fr.openmc.core.utils.text.messages.Prefix; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; +import revxrsal.commands.annotation.Command; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.annotation.SuggestWith; +import revxrsal.commands.bukkit.annotation.CommandPermission; + +@Command({"ambient", "customambient"}) +@CommandPermission("omc.admins.commands.customambient") +public class CustomAmbientCommands { + @Subcommand("apply") + public void applyAmbient( + Player player, + Player toPlayer, + @SuggestWith(CustomAmbientAutoComplete.class) String id + ) { + CustomAmbient ambient = OMCRegistry.CUSTOM_AMBIENTS.get(id); + + if (ambient == null) { + MessagesManager.sendMessage(player, TranslationManager.translation("command.registry.custom_ambient.apply.null"), Prefix.STAFF, MessageType.ERROR, true); + return; + } + + ambient.apply(toPlayer); + MessagesManager.sendMessage(player, TranslationManager.translation("command.registry.custom_ambient.apply.success", + Component.text(id).color(NamedTextColor.YELLOW), + Component.text(toPlayer.getName()).color(NamedTextColor.YELLOW) + ), Prefix.STAFF, MessageType.ERROR, true); + } + + @Subcommand("reset") + public void resetAmbient( + Player player, + Player toPlayer + ) { + if (!CustomAmbient.ACTIVE_AMBIENTS.containsKey(toPlayer.getUniqueId())) { + MessagesManager.sendMessage(player, TranslationManager.translation("command.registry.custom_ambient.reset.player_havnt_ambient"), Prefix.STAFF, MessageType.ERROR, true); + return; + } + + String idAmbient = CustomAmbient.ACTIVE_AMBIENTS.get(toPlayer.getUniqueId()); + + OMCRegistry.CUSTOM_AMBIENTS.get(idAmbient).reset(toPlayer); + MessagesManager.sendMessage(player, TranslationManager.translation("command.registry.custom_ambient.reset.success", + Component.text(toPlayer.getName()).color(NamedTextColor.YELLOW) + ), Prefix.STAFF, MessageType.ERROR, true); + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/commands/autocomplete/CustomAmbientAutoComplete.java b/src/main/java/fr/openmc/core/registry/ambient/commands/autocomplete/CustomAmbientAutoComplete.java new file mode 100644 index 000000000..e20a0b047 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/commands/autocomplete/CustomAmbientAutoComplete.java @@ -0,0 +1,21 @@ +package fr.openmc.core.registry.ambient.commands.autocomplete; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.ambient.CustomAmbient; +import org.jetbrains.annotations.NotNull; +import revxrsal.commands.autocomplete.SuggestionProvider; +import revxrsal.commands.bukkit.actor.BukkitCommandActor; +import revxrsal.commands.node.ExecutionContext; + +import java.util.List; + +public class CustomAmbientAutoComplete implements SuggestionProvider { + + @Override + public @NotNull List getSuggestions(@NotNull ExecutionContext context) { + return OMCRegistry.CUSTOM_AMBIENTS.values() + .stream() + .map(CustomAmbient::getId) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/ambient/contents/DarkAmbient.java b/src/main/java/fr/openmc/core/registry/ambient/contents/DarkAmbient.java new file mode 100644 index 000000000..4e84828fe --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/contents/DarkAmbient.java @@ -0,0 +1,36 @@ +package fr.openmc.core.registry.ambient.contents; + +import fr.openmc.api.datapacks.injectors.DimensionTypesInjector; +import fr.openmc.core.registry.ambient.CustomAmbient; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; + +public class DarkAmbient extends CustomAmbient { + @Override + public String getId() { + return "dark_ambient"; + } + + @Override + public DimensionTypesInjector.DimensionTypeBuilder getDimensionTypeBuilder() { + return new DimensionTypesInjector.DimensionTypeBuilder() + .attributes(obj -> { + obj.addProperty("visual/ambient_light_color", "#DD37E6"); + obj.addProperty("visual/sky_color", "#DD37E6"); + obj.addProperty("visual/sky_light_color", "#3B205E"); + obj.addProperty("visual/fog_start_distance", 40); + obj.addProperty("visual/fog_end_distance", 70); + obj.addProperty("visual/sunrise_sunset_color", "#FFBB00FA"); + }) + .defaultClock(null) + .timelines(null) + .skybox(DimensionType.Skybox.END) + .hasSkylight(true); + } + + @Override + public ResourceKey getTransitionDimension() { + return Level.END; + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/contents/HellAmbient.java b/src/main/java/fr/openmc/core/registry/ambient/contents/HellAmbient.java new file mode 100644 index 000000000..98aba1cf3 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/contents/HellAmbient.java @@ -0,0 +1,44 @@ +package fr.openmc.core.registry.ambient.contents; + +import fr.openmc.api.datapacks.injectors.DimensionTypesInjector; +import fr.openmc.core.registry.ambient.CustomAmbient; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import org.bukkit.Particle; + +public class HellAmbient extends CustomAmbient { + @Override + public String getId() { + return "hell_ambient"; + } + + @Override + public DimensionTypesInjector.DimensionTypeBuilder getDimensionTypeBuilder() { + return new DimensionTypesInjector.DimensionTypeBuilder() + .attributes(obj -> { + obj.addProperty("visual/ambient_light_color", "#A3170B"); + obj.addProperty("visual/block_light_tint", "#F53200"); + obj.addProperty("visual/fog_start_distance", 10); + obj.addProperty("visual/fog_end_distance", 96); + obj.addProperty("minecraft:visual/sky_light_color", "#7a7aff"); + obj.addProperty("minecraft:visual/sky_light_factor", 0); + obj.addProperty("visual/fog_color","#5E1414"); + }) + .ambientParticles(Particle.CRIMSON_SPORE, 0.25f) + .defaultClock(null) + .ambientLight(0.1f) + .cardinalLight("nether") + .timelines("#minecraft:in_nether") + .skybox(DimensionType.Skybox.NONE) + .infiniburn("#minecraft:infiniburn_nether") + .hasSkylight(false) + .hasCeiling(true) + .hasFixedTime(true); + } + + @Override + public ResourceKey getTransitionDimension() { + return Level.NETHER; + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/listeners/CustomAmbientListener.java b/src/main/java/fr/openmc/core/registry/ambient/listeners/CustomAmbientListener.java new file mode 100644 index 000000000..1a9a9c2f3 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/listeners/CustomAmbientListener.java @@ -0,0 +1,20 @@ +package fr.openmc.core.registry.ambient.listeners; + +import fr.openmc.core.registry.ambient.CustomAmbient; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class CustomAmbientListener implements Listener { + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + CustomAmbient.ACTIVE_AMBIENTS.remove(event.getPlayer().getUniqueId()); + } + + @EventHandler + public void onPlayerChangeWorld(PlayerChangedWorldEvent event) { + CustomAmbient.ACTIVE_AMBIENTS.remove(event.getPlayer().getUniqueId()); + } +} + diff --git a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java index 691754efc..fe98ee9b2 100644 --- a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java +++ b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java @@ -2,6 +2,7 @@ import fr.openmc.core.OMCPlugin; import fr.openmc.core.OMCRegistry; +import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; import fr.openmc.core.features.dream.registries.enchantements.DreamSleeper; import fr.openmc.core.features.dream.registries.enchantements.Experientastic; @@ -17,11 +18,16 @@ import org.bukkit.inventory.EquipmentSlotGroup; @SuppressWarnings("UnstableApiUsage") -public class CustomEnchantmentRegistry extends Registry { +public class CustomEnchantmentRegistry extends Registry implements KeyedRegistry { + + @Override + public Key key(CustomEnchantment registryObject) { + return registryObject.getKey(); + } @Override public void bootstrap(BootstrapContext context) { - registerAll( + register( new Soulbound(), new Experientastic(), new DreamSleeper() @@ -64,10 +70,4 @@ public void postInit() { } } } - - public void registerAll(CustomEnchantment... enchantments) { - for (CustomEnchantment e : enchantments) { - register(e.getKey(), e); - } - } } \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java b/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java index 7236ba298..a49ffd7dc 100644 --- a/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java +++ b/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java @@ -2,6 +2,7 @@ import dev.lone.itemsadder.api.CustomStack; import fr.openmc.core.CommandsManager; +import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; import fr.openmc.core.hooks.itemsadder.ItemsAdderHook; import fr.openmc.core.registry.items.contents.AywenCap; @@ -15,11 +16,15 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; -public class CustomItemRegistry extends Registry { +public class CustomItemRegistry extends Registry implements KeyedRegistry { public static final NamespacedKey CUSTOM_ITEM_KEY = new NamespacedKey("openmc", "custom_item"); + @Override + public String key(CustomItem registryObject) { + return registryObject.getId(); + } @Override public void postInit() { CommandsManager.getHandler().register(new CustomItemsDebugCommand()); @@ -118,10 +123,6 @@ public CustomItem get(ItemStack stack) { } } - public void register(CustomItem item) { - register(item.getId(), item); - } - public void register(String name, ItemStack item) { register(name, new CustomItem(name) { @Override @@ -147,10 +148,4 @@ public ItemStack getVanilla() { } }); } - - public void register(Iterable items) { - for (CustomItem item : items) { - register(item); - } - } } diff --git a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java index e79b25177..f966c85cd 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java +++ b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java @@ -1,8 +1,9 @@ package fr.openmc.core.registry.loottable; +import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; -public class CustomLootTableRegistry extends Registry { +public class CustomLootTableRegistry extends Registry implements KeyedRegistry { @Override public void postInit() { @@ -10,13 +11,8 @@ public void postInit() { } - public void register(CustomLootTable table) { - register(table.getName(), table); - } - - public void register(CustomLootTable... tables) { - for (CustomLootTable table : tables) { - register(table); - } + @Override + public String key(CustomLootTable registryObject) { + return registryObject.getName(); } } \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java b/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java index f82fad275..e09ac6c9e 100644 --- a/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java +++ b/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java @@ -1,6 +1,7 @@ package fr.openmc.core.registry.mobs; import fr.openmc.core.OMCPlugin; +import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; import org.bukkit.NamespacedKey; import org.bukkit.entity.Entity; @@ -8,7 +9,7 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; -public class CustomMobRegistry extends Registry { +public class CustomMobRegistry extends Registry implements KeyedRegistry { public static final NamespacedKey CUSTOM_MOB_KEY = new NamespacedKey("openmc", "custom_mob"); @@ -18,6 +19,12 @@ public void postInit() { // ** REGISTER MOBS ** } + @Override + public String key(CustomMobEntry registryObject) { + return registryObject.id(); + } + + @Override public void register(CustomMobEntry mob) { if (mob.factory().apply(mob.id()) instanceof Listener listener) { OMCPlugin.registerEvents(listener); @@ -25,18 +32,6 @@ public void register(CustomMobEntry mob) { register(mob.id(), mob); } - public void register(Iterable mobs) { - for (CustomMobEntry mob : mobs) { - register(mob); - } - } - - public void register(CustomMobEntry... mobs) { - for (CustomMobEntry mob : mobs) { - register(mob); - } - } - public CustomMob getMob(String id) { CustomMobEntry entry = get(id); return entry != null ? entry.factory().apply(id) : null; diff --git a/src/main/java/fr/openmc/core/utils/nms/PlayerPositionNMS.java b/src/main/java/fr/openmc/core/utils/nms/PlayerPositionNMS.java new file mode 100644 index 000000000..321e223e2 --- /dev/null +++ b/src/main/java/fr/openmc/core/utils/nms/PlayerPositionNMS.java @@ -0,0 +1,33 @@ +package fr.openmc.core.utils.nms; + +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.Set; + +/** + * Classe receuillant les NMS lié au packet {@link ClientboundPlayerPositionPacket} + * Afin de simplifier l'utilisation des NMS + */ +public class PlayerPositionNMS { + public static void sendPos(ServerPlayer nmsPlayer, Vec3 location) { + PositionMoveRotation positionMoveRotation = new PositionMoveRotation( + location, + Vec3.ZERO, + nmsPlayer.getYRot(), + nmsPlayer.getXRot() + ); + + // je prefere utiliser la méthode au lieu de {@link ClientboundPlayerPositionPacket} + // car elle néccéssité un ID, qui je suppose ne doit pas etre dupli + nmsPlayer.connection.teleport(positionMoveRotation, Set.of()); + } + + public static void sendPos(Player player, Location location) { + sendPos((ServerPlayer) player, new Vec3(location.getX(), location.getY(), location.getZ())); + } +} diff --git a/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java b/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java new file mode 100644 index 000000000..0cec2d3e0 --- /dev/null +++ b/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java @@ -0,0 +1,157 @@ +package fr.openmc.core.utils.nms; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.registry.ambient.CustomAmbient; +import net.minecraft.network.protocol.game.*; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.storage.LevelData; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +/** + * Classe receuillant les NMS lié au packet {@link ClientboundRespawnPacket} + * Afin de simplifier l'utilisation des NMS + * + * @see CustomAmbient utilisation trés spécifique, on affiche une dimension_type sur le joueur, ds la dimension actuelle + */ +public class PlayerRespawnNMS { + + /** + * Envoie juste le packet respawn. + * @param nmsPlayer le joueur en NMS + * @param spawnInfo Les informations de la dimension ou il est envoyé + */ + private static void sendSimplePacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo spawnInfo) { + nmsPlayer.connection.send(new ClientboundRespawnPacket( + spawnInfo, + ClientboundRespawnPacket.KEEP_ALL_DATA + )); + } + + /** + * Envoie le packet RESPAWN au joueur ciblé, avec des informations données + * En utilisant des procédures qui assurent le tout + * @param nmsPlayer le joueur en NMS + * @param spawnInfo Les informations de la dimension ou il est envoyé + */ + public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo spawnInfo) { + // ** Envoie de l'entete du packet respawn + sendSimplePacket(nmsPlayer, spawnInfo); + + // ** Procédure afin que le packet respawn soit valide + sendPostRespawnPackets(nmsPlayer); + resyncEntities(nmsPlayer.getBukkitEntity().getPlayer()); + } + + + /** + * Envoie le packet RESPAWN au joueur ciblé avec les informations de spawn, + * plus un pivot qui permet d'afficher un changement de dimension + * @param nmsPlayer le joueur ciblé + * @param targetSpawnInfo les informations de spawn + * @param pivotDimension la dimension de pivot + * Note : Assurer vous que le pivot ne pointe pas vers une dimension ou le joueur est déja + * {@code nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()).dimension().equals(Level.OVERWORLD) ? Level.END : Level.OVERWORLD;} + */ + public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo targetSpawnInfo, ResourceKey pivotDimension) { + // changement de dimension car sinon l'ambience de la dimension n'est pas affiché + // l'unique packet de repsawn pour simuler un changement de dimension + envoie du dimension type = plus rapide + sendSimplePacket(nmsPlayer, createPlayerSpawnInfoWithDimension(nmsPlayer, pivotDimension)); + sendPacket(nmsPlayer, targetSpawnInfo); + } + + /** + * Reaffiche les entités autour du joueur + * @param player le joueur ciblé + */ + private static void resyncEntities(Player player) { + new BukkitRunnable() { + @Override + public void run() { + if (!player.isOnline()) return; + + double range = 96.0D; + for (Entity entity : player.getNearbyEntities(range, range, range)) { + if (entity.equals(player)) continue; + + player.hideEntity(OMCPlugin.getInstance(), entity); + player.showEntity(OMCPlugin.getInstance(), entity); + } + } + }.runTaskLater(OMCPlugin.getInstance(), 2L); + } + + /** + * Procédure basée sur {@link ServerPlayer#teleport} afin de corriger que le packet RESPAWN invalide + * @param nmsPlayer le joueur (NMS) + */ + private static void sendPostRespawnPackets(ServerPlayer nmsPlayer) { + ServerLevel nmsWorld = nmsPlayer.level(); + LevelData levelData = nmsWorld.getLevelData(); + + // ** Lancement des différentes procédure nécessaire apres un Repsawn Packet + nmsPlayer.connection.send(new ClientboundChangeDifficultyPacket( + levelData.getDifficulty(), levelData.isDifficultyLocked() + )); + + PlayerList playerList = ((CraftServer) Bukkit.getServer()).getServer().getPlayerList(); + playerList.sendPlayerPermissionLevel(nmsPlayer); + nmsPlayer.connection.send(new ClientboundPlayerAbilitiesPacket(nmsPlayer.getAbilities())); + + playerList.sendLevelInfo(nmsPlayer, nmsWorld); + playerList.sendAllPlayerInfo(nmsPlayer); + playerList.sendActivePlayerEffects(nmsPlayer); + + PlayerPositionNMS.sendPos(nmsPlayer, nmsPlayer.position()); + + nmsPlayer.connection.send(new ClientboundSetChunkCacheCenterPacket( + nmsPlayer.chunkPosition().x(), + nmsPlayer.chunkPosition().z() + )); + + int viewDistance = nmsWorld.getServer().getPlayerList().getViewDistance(); + ChunkPos center = nmsPlayer.chunkPosition(); + for (int cx = center.x() - viewDistance; cx <= center.x() + viewDistance; cx++) { + for (int cz = center.z() - viewDistance; cz <= center.z() + viewDistance; cz++) { + LevelChunk chunk = nmsWorld.getChunkIfLoaded(cx, cz); + if (chunk != null) { + nmsPlayer.connection.send( + new ClientboundLevelChunkWithLightPacket(chunk, nmsWorld.getLightEngine(), null, null, false) + ); + } + } + } + } + + /** + * Créer les Informations de Spawn avec la dimension ciblé, en gardant les autres informations de base + * @param nmsPlayer le joueur ciblé + * @param dimensionKey la dimension type key + * @return Information de Spawn modifié + */ + private static CommonPlayerSpawnInfo createPlayerSpawnInfoWithDimension(ServerPlayer nmsPlayer, ResourceKey dimensionKey) { + CommonPlayerSpawnInfo base = nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()); + + return new CommonPlayerSpawnInfo( + base.dimensionType(), + dimensionKey, + base.seed(), + base.gameType(), + base.previousGameType(), + base.isDebug(), + base.isFlat(), + base.lastDeathLocation(), + base.portalCooldown(), + base.seaLevel() + ); + } +} diff --git a/src/main/resources/translations/default/commands.properties b/src/main/resources/translations/default/commands.properties index df2e5fd92..09b447603 100644 --- a/src/main/resources/translations/default/commands.properties +++ b/src/main/resources/translations/default/commands.properties @@ -19,6 +19,11 @@ command.debug.chronometer.cant_90s_chronometer=Ne pas dépasser plus de 90s command.debug.cooldown.success=Succès, le cooldown est activé command.debug.cooldown.error=Erreur, vous pouvez refaire la commande +command.registry.custom_ambient.apply.null=Erreur, l'id renseigné est nul +command.registry.custom_ambient.apply.success=L'ambiance %1$s a été correctement appliqué sur %2$s ! +command.registry.custom_ambient.reset.player_havnt_ambient=Erreur, le joueur n'a pas d'ambiance appliquée +command.registry.custom_ambient.reset.success=L'ambiance a été correctement enlevé sur %1$s ! + command.utils.cooldowns.no_cooldown=Vous n'avez aucun cooldown actif. command.utils.cooldowns.list_cooldowns=Liste des cooldowns actifs : command.utils.cooldowns.list=- %1$s : %2$s