+1
build-logic/src/main/kotlin/shadow-platform.gradle.kts
+1
build-logic/src/main/kotlin/shadow-platform.gradle.kts
+2
gradle/libs.versions.toml
+2
gradle/libs.versions.toml
···
4
4
shadow = "8.3.0"
5
5
6
6
paper = "1.21.4-R0.1-SNAPSHOT"
7
+
configurate = "4.2.0-SNAPSHOT"
7
8
8
9
[libraries]
9
10
# build logic
···
17
18
18
19
# libraries
19
20
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
21
+
configurate-hocon = { group = "org.spongepowered", name = "configurate-hocon", version.ref = "configurate" }
+2
-2
license_header.txt
+2
-2
license_header.txt
···
1
-
<one line to give the program's name and a brief idea of what it does.>
1
+
Soulbinding
2
2
3
-
Copyright (C) <year> <name of author>
3
+
Copyright (C) 2024 kokiriglade
4
4
5
5
This program is free software: you can redistribute it and/or modify
6
6
it under the terms of the GNU General Public License as published by
+4
-7
plugin/build.gradle.kts
+4
-7
plugin/build.gradle.kts
···
1
-
//import io.papermc.paperweight.userdev.ReobfArtifactConfiguration.Companion.MOJANG_PRODUCTION // paperweight
2
-
3
1
plugins {
4
2
id("shadow-platform")
5
3
id("xyz.jpenilla.resource-factory-paper-convention") version "1.2.0" // paper plugin
6
4
id("xyz.jpenilla.run-paper") version "2.3.1"
7
5
}
8
6
9
-
//paperweight.reobfArtifactConfiguration = MOJANG_PRODUCTION // paperweight
10
-
11
7
dependencies {
12
-
// paperweight.paperDevBundle(libs.versions.paper.get()) // paperweight
13
8
compileOnly(libs.paper)
14
9
implementation(project(":${rootProject.name}-api"))
10
+
implementation(libs.configurate.hocon)
15
11
}
16
12
17
13
tasks {
···
24
20
}
25
21
26
22
paperPluginYaml {
27
-
main = "${rootProject.group}.${rootProject.name}.TemplatePlugin"
23
+
main = "${rootProject.group}.${rootProject.name}.Soulbinding"
24
+
bootstrapper = "${rootProject.group}.${rootProject.name}.SoulbindingBootstrap"
28
25
name = rootProject.name
29
26
authors.add("kokiriglade")
30
-
apiVersion = libs.versions.paper.get()
27
+
apiVersion = libs.versions.paper.get().split("-R0.1-SNAPSHOT")[0]
31
28
}
+104
plugin/src/main/java/de/kokirigla/soulbinding/SoulbindingBootstrap.java
+104
plugin/src/main/java/de/kokirigla/soulbinding/SoulbindingBootstrap.java
···
1
+
/*
2
+
* Soulbinding
3
+
*
4
+
* Copyright (C) 2024 kokiriglade
5
+
*
6
+
* This program is free software: you can redistribute it and/or modify
7
+
* it under the terms of the GNU General Public License as published by
8
+
* the Free Software Foundation, either version 3 of the License, or
9
+
* (at your option) any later version.
10
+
*
11
+
* This program is distributed in the hope that it will be useful,
12
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+
* GNU General Public License for more details.
15
+
*
16
+
* You should have received a copy of the GNU General Public License
17
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
*/
19
+
package de.kokirigla.soulbinding;
20
+
21
+
import de.kokirigla.soulbinding.configuration.ConfigHelper;
22
+
import de.kokirigla.soulbinding.configuration.MainConfig;
23
+
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
24
+
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
25
+
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
26
+
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
27
+
import io.papermc.paper.registry.RegistryKey;
28
+
import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
29
+
import io.papermc.paper.registry.event.RegistryEvents;
30
+
import io.papermc.paper.registry.keys.EnchantmentKeys;
31
+
import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys;
32
+
import io.papermc.paper.registry.set.RegistrySet;
33
+
import io.papermc.paper.registry.tag.TagKey;
34
+
import io.papermc.paper.tag.TagEntry;
35
+
import net.kyori.adventure.key.Key;
36
+
import org.bukkit.inventory.EquipmentSlotGroup;
37
+
import org.bukkit.inventory.ItemType;
38
+
import org.jspecify.annotations.NullMarked;
39
+
import org.jspecify.annotations.Nullable;
40
+
41
+
import java.util.Objects;
42
+
import java.util.stream.Collectors;
43
+
import java.util.stream.Stream;
44
+
45
+
@NullMarked
46
+
public final class SoulbindingBootstrap implements PluginBootstrap {
47
+
48
+
public static final Key SOULBOUND_ENCHANTMENT = Key.key("soulbinding:soulbound");
49
+
public static final TagKey<ItemType> SOULBOUNDABLE_TAG = ItemTypeTagKeys.create(Key.key("soulbinding:soulboundable"));
50
+
51
+
private @Nullable MainConfig config;
52
+
53
+
@Override
54
+
public void bootstrap(final BootstrapContext context) {
55
+
this.config = ConfigHelper.loadConfig(MainConfig.class, context.getDataDirectory().resolve("config.conf"));
56
+
ConfigHelper.saveConfig(context.getDataDirectory().resolve("config.conf"), this.config);
57
+
58
+
context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.freeze().newHandler(event -> {
59
+
event.registry().register(
60
+
EnchantmentKeys.create(SOULBOUND_ENCHANTMENT),
61
+
b -> b.description(config.soulboundDescription())
62
+
.supportedItems(event.getOrCreateTag(SOULBOUNDABLE_TAG))
63
+
.anvilCost(3)
64
+
.maxLevel(1)
65
+
.weight(1)
66
+
.exclusiveWith(
67
+
RegistrySet.keySet(
68
+
RegistryKey.ENCHANTMENT,
69
+
EnchantmentKeys.BINDING_CURSE,
70
+
EnchantmentKeys.VANISHING_CURSE
71
+
)
72
+
)
73
+
.minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1))
74
+
.maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(3, 1))
75
+
.activeSlots(EquipmentSlotGroup.ANY)
76
+
);
77
+
}));
78
+
79
+
context.getLifecycleManager().registerEventHandler(LifecycleEvents.TAGS.preFlatten(
80
+
RegistryKey.ITEM).newHandler(event -> {
81
+
event.registrar().addToTag(
82
+
SOULBOUNDABLE_TAG,
83
+
Stream.of(
84
+
ItemTypeTagKeys.ENCHANTABLE_WEAPON,
85
+
ItemTypeTagKeys.ENCHANTABLE_MINING,
86
+
ItemTypeTagKeys.ENCHANTABLE_TRIDENT,
87
+
ItemTypeTagKeys.ENCHANTABLE_BOW,
88
+
ItemTypeTagKeys.ENCHANTABLE_CROSSBOW,
89
+
ItemTypeTagKeys.ENCHANTABLE_EQUIPPABLE,
90
+
ItemTypeTagKeys.LECTERN_BOOKS
91
+
)
92
+
.map(TagEntry::tagEntry)
93
+
.collect(Collectors.toSet())
94
+
);
95
+
}));
96
+
}
97
+
98
+
@Override
99
+
public Soulbinding createPlugin(final PluginProviderContext context) {
100
+
Objects.requireNonNull(config);
101
+
return new Soulbinding(config);
102
+
}
103
+
104
+
}
+17
-4
plugin/src/main/java/de/kokirigla/soulbinding/SoulbindingPlugin.java
plugin/src/main/java/de/kokirigla/soulbinding/Soulbinding.java
+17
-4
plugin/src/main/java/de/kokirigla/soulbinding/SoulbindingPlugin.java
plugin/src/main/java/de/kokirigla/soulbinding/Soulbinding.java
···
1
1
/*
2
-
* <one line to give the program's name and a brief idea of what it does.>
2
+
* Soulbinding
3
3
*
4
-
* Copyright (C) <year> <name of author>
4
+
* Copyright (C) 2024 kokiriglade
5
5
*
6
6
* This program is free software: you can redistribute it and/or modify
7
7
* it under the terms of the GNU General Public License as published by
···
18
18
*/
19
19
package de.kokirigla.soulbinding;
20
20
21
+
import de.kokirigla.soulbinding.configuration.MainConfig;
22
+
import de.kokirigla.soulbinding.listener.DeathListener;
21
23
import org.bukkit.plugin.java.JavaPlugin;
22
24
import org.jspecify.annotations.NullMarked;
23
25
24
26
@NullMarked
25
-
public final class SoulbindingPlugin extends JavaPlugin {
27
+
public final class Soulbinding extends JavaPlugin {
28
+
29
+
private final MainConfig config;
30
+
31
+
public Soulbinding(final MainConfig config) {
32
+
super();
33
+
this.config = config;
34
+
}
26
35
27
36
@Override
28
37
public void onEnable() {
29
-
getSLF4JLogger().info("Hello World!");
38
+
getServer().getPluginManager().registerEvents(new DeathListener(this), this);
39
+
}
40
+
41
+
public MainConfig config() {
42
+
return config;
30
43
}
31
44
32
45
}
+109
plugin/src/main/java/de/kokirigla/soulbinding/configuration/ConfigHelper.java
+109
plugin/src/main/java/de/kokirigla/soulbinding/configuration/ConfigHelper.java
···
1
+
/*
2
+
* Soulbinding
3
+
*
4
+
* Copyright (C) 2024 kokiriglade
5
+
*
6
+
* This program is free software: you can redistribute it and/or modify
7
+
* it under the terms of the GNU General Public License as published by
8
+
* the Free Software Foundation, either version 3 of the License, or
9
+
* (at your option) any later version.
10
+
*
11
+
* This program is distributed in the hope that it will be useful,
12
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+
* GNU General Public License for more details.
15
+
*
16
+
* You should have received a copy of the GNU General Public License
17
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
*/
19
+
package de.kokirigla.soulbinding.configuration;
20
+
21
+
import io.leangen.geantyref.TypeToken;
22
+
import org.jspecify.annotations.NullMarked;
23
+
import org.jspecify.annotations.Nullable;
24
+
import org.spongepowered.configurate.CommentedConfigurationNode;
25
+
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
26
+
import org.spongepowered.configurate.objectmapping.ObjectMapper;
27
+
import org.spongepowered.configurate.util.NamingSchemes;
28
+
29
+
import java.nio.file.Files;
30
+
import java.nio.file.Path;
31
+
import java.util.Objects;
32
+
import java.util.function.Supplier;
33
+
34
+
@SuppressWarnings("unused")
35
+
@NullMarked
36
+
public final class ConfigHelper {
37
+
38
+
public static HoconConfigurationLoader createLoader(final Path file) {
39
+
final ObjectMapper.Factory factory = ObjectMapper.factoryBuilder()
40
+
.defaultNamingScheme(NamingSchemes.SNAKE_CASE)
41
+
.build();
42
+
43
+
return HoconConfigurationLoader.builder()
44
+
.defaultOptions(options -> options.serializers(build -> build.registerAnnotatedObjects(factory)))
45
+
.path(file)
46
+
.build();
47
+
}
48
+
49
+
50
+
public static <T> T loadConfig(final TypeToken<T> configType,
51
+
final Path path,
52
+
final Supplier<T> defaultConfigFactory) {
53
+
try {
54
+
if (Files.isRegularFile(path)) {
55
+
final HoconConfigurationLoader loader = createLoader(path);
56
+
final CommentedConfigurationNode node = loader.load();
57
+
return Objects.requireNonNull(node.get(configType));
58
+
} else {
59
+
return defaultConfigFactory.get();
60
+
}
61
+
} catch (final Exception ex) {
62
+
throw new RuntimeException("Failed to load config of type '" + configType.getType()
63
+
.getTypeName() + "' from file at '" + path + "'.", ex);
64
+
}
65
+
}
66
+
67
+
// For @ConfigSerializable types with no args constructor
68
+
public static <T> T loadConfig(final Class<T> configType, final Path path) {
69
+
return loadConfig(TypeToken.get(configType), path, () -> {
70
+
try {
71
+
return configType.getConstructor().newInstance();
72
+
} catch (final ReflectiveOperationException ex) {
73
+
throw new RuntimeException("Failed to create instance of type " + configType.getName() + ", does it have a public no args constructor?");
74
+
}
75
+
});
76
+
}
77
+
78
+
public static <T> void saveConfig(final Path path,
79
+
final TypeToken<T> configType,
80
+
final T config) {
81
+
saveConfig(path, config, configType);
82
+
}
83
+
84
+
// For @ConfigSerializable types
85
+
public static void saveConfig(final Path path, final Object config) {
86
+
saveConfig(path, config, null);
87
+
}
88
+
89
+
@SuppressWarnings({"unchecked", "rawtypes"})
90
+
private static void saveConfig(final Path path,
91
+
final Object config,
92
+
final @Nullable TypeToken<?> configType) {
93
+
try {
94
+
Files.createDirectories(path.getParent());
95
+
final HoconConfigurationLoader loader = createLoader(path);
96
+
final CommentedConfigurationNode node = loader.createNode();
97
+
if (configType != null) {
98
+
node.set((TypeToken) configType, config);
99
+
} else {
100
+
node.set(config);
101
+
}
102
+
loader.save(node);
103
+
} catch (final Exception ex) {
104
+
throw new RuntimeException("Failed to save config of type '" + (configType != null ? configType.getType()
105
+
.getTypeName() : config.getClass().getName()) + "' to file at '" + path + "'.", ex);
106
+
}
107
+
}
108
+
109
+
}
+48
plugin/src/main/java/de/kokirigla/soulbinding/configuration/MainConfig.java
+48
plugin/src/main/java/de/kokirigla/soulbinding/configuration/MainConfig.java
···
1
+
/*
2
+
* Soulbinding
3
+
*
4
+
* Copyright (C) 2024 kokiriglade
5
+
*
6
+
* This program is free software: you can redistribute it and/or modify
7
+
* it under the terms of the GNU General Public License as published by
8
+
* the Free Software Foundation, either version 3 of the License, or
9
+
* (at your option) any later version.
10
+
*
11
+
* This program is distributed in the hope that it will be useful,
12
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+
* GNU General Public License for more details.
15
+
*
16
+
* You should have received a copy of the GNU General Public License
17
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
*/
19
+
package de.kokirigla.soulbinding.configuration;
20
+
21
+
import net.kyori.adventure.text.Component;
22
+
import net.kyori.adventure.text.minimessage.MiniMessage;
23
+
import org.jspecify.annotations.NullMarked;
24
+
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
25
+
import org.spongepowered.configurate.objectmapping.meta.Comment;
26
+
27
+
@ConfigSerializable
28
+
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
29
+
@NullMarked
30
+
public final class MainConfig {
31
+
32
+
private String soulboundDescription = "<lang:enchantment.soulbinding.soulbound>";
33
+
34
+
@Comment("Chance of a Soulbound enchantment book being dropped upon killing the ender dragon. 10% by default")
35
+
private double chance = 0.10d;
36
+
37
+
public MainConfig() {
38
+
}
39
+
40
+
public Component soulboundDescription() {
41
+
return MiniMessage.miniMessage().deserialize(this.soulboundDescription);
42
+
}
43
+
44
+
public double chance() {
45
+
return this.chance;
46
+
}
47
+
48
+
}
+92
plugin/src/main/java/de/kokirigla/soulbinding/listener/DeathListener.java
+92
plugin/src/main/java/de/kokirigla/soulbinding/listener/DeathListener.java
···
1
+
/*
2
+
* Soulbinding
3
+
*
4
+
* Copyright (C) 2024 kokiriglade
5
+
*
6
+
* This program is free software: you can redistribute it and/or modify
7
+
* it under the terms of the GNU General Public License as published by
8
+
* the Free Software Foundation, either version 3 of the License, or
9
+
* (at your option) any later version.
10
+
*
11
+
* This program is distributed in the hope that it will be useful,
12
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+
* GNU General Public License for more details.
15
+
*
16
+
* You should have received a copy of the GNU General Public License
17
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
*/
19
+
package de.kokirigla.soulbinding.listener;
20
+
21
+
import de.kokirigla.soulbinding.Soulbinding;
22
+
import de.kokirigla.soulbinding.SoulbindingBootstrap;
23
+
import io.papermc.paper.datacomponent.DataComponentTypes;
24
+
import io.papermc.paper.datacomponent.item.ItemEnchantments;
25
+
import io.papermc.paper.registry.RegistryAccess;
26
+
import io.papermc.paper.registry.RegistryKey;
27
+
import org.bukkit.Material;
28
+
import org.bukkit.enchantments.Enchantment;
29
+
import org.bukkit.entity.EnderDragon;
30
+
import org.bukkit.event.EventHandler;
31
+
import org.bukkit.event.EventPriority;
32
+
import org.bukkit.event.Listener;
33
+
import org.bukkit.event.entity.EntityDeathEvent;
34
+
import org.bukkit.event.entity.PlayerDeathEvent;
35
+
import org.bukkit.inventory.ItemStack;
36
+
37
+
import java.util.HashSet;
38
+
import java.util.Set;
39
+
40
+
public final class DeathListener implements Listener {
41
+
42
+
private final Soulbinding plugin;
43
+
44
+
public DeathListener(final Soulbinding plugin) {
45
+
this.plugin = plugin;
46
+
}
47
+
48
+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
49
+
public void onPlayerDeath(final PlayerDeathEvent event) {
50
+
final Set<ItemStack> soulboundItems = new HashSet<>();
51
+
52
+
for (final ItemStack drop : event.getDrops()) {
53
+
if(drop.getEnchantments().containsKey(this.soulboundEnchantment())) {
54
+
soulboundItems.add(drop);
55
+
}
56
+
}
57
+
58
+
soulboundItems.forEach(item -> {
59
+
event.getDrops().remove(item);
60
+
event.getItemsToKeep().add(item);
61
+
});
62
+
}
63
+
64
+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
65
+
public void onEnderDragonDeath(final EntityDeathEvent event) {
66
+
if(event.getEntity() instanceof EnderDragon dragon) {
67
+
if (Math.random() <= plugin.config().chance()) {
68
+
dragon.getLocation().getWorld().dropItemNaturally(
69
+
dragon.getLocation(),
70
+
createSoulboundBook()
71
+
);
72
+
}
73
+
}
74
+
}
75
+
76
+
private ItemStack createSoulboundBook() {
77
+
final ItemStack book = ItemStack.of(Material.ENCHANTED_BOOK);
78
+
book.setData(
79
+
DataComponentTypes.STORED_ENCHANTMENTS,
80
+
ItemEnchantments.itemEnchantments()
81
+
.add(soulboundEnchantment(), 1)
82
+
.build()
83
+
);
84
+
return book;
85
+
}
86
+
87
+
private Enchantment soulboundEnchantment() {
88
+
return RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).getOrThrow(
89
+
SoulbindingBootstrap.SOULBOUND_ENCHANTMENT);
90
+
}
91
+
92
+
}
+8
-1
settings.gradle.kts
+8
-1
settings.gradle.kts
···
1
1
dependencyResolutionManagement {
2
2
repositories {
3
3
mavenCentral()
4
-
5
4
maven {
6
5
name = "papermc"
7
6
url = uri("https://repo.papermc.io/repository/maven-public/")
7
+
}
8
+
maven {
9
+
name = "spongepowered"
10
+
url = uri("https://repo.spongepowered.org/maven/")
11
+
12
+
mavenContent {
13
+
includeModule("org.spongepowered", "configurate-hocon")
14
+
}
8
15
}
9
16
}
10
17
}