some fixes
This commit is contained in:
parent
9b8fd5730f
commit
1182c7e8fe
@ -1,9 +1,9 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
id("com.google.protobuf") version "0.9.6"
|
||||
id("com.google.protobuf") version "0.10.0"
|
||||
}
|
||||
|
||||
val protobufVersion = "4.33.5"
|
||||
val protobufVersion = "4.35.1"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@ -47,7 +47,7 @@ repositories {
|
||||
dependencies {
|
||||
implementation("io.papermc.paper:paper-api:26.2.build.+")
|
||||
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
||||
implementation("io.github.astonbitecode:j4rs:0.24.0")
|
||||
implementation("io.github.astonbitecode:j4rs:0.25.0")
|
||||
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
||||
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
||||
implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
|
||||
|
||||
@ -0,0 +1,165 @@
|
||||
package org.patchbukkit;
|
||||
|
||||
import com.destroystokyo.paper.SkinParts;
|
||||
import com.destroystokyo.paper.util.VersionFetcher;
|
||||
import io.papermc.paper.InternalAPIBridge;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.datacomponent.item.ResolvableProfile;
|
||||
import io.papermc.paper.entity.poi.PoiType;
|
||||
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
|
||||
import io.papermc.paper.world.damagesource.CombatEntry;
|
||||
import io.papermc.paper.world.damagesource.FallLocationType;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.attribute.Attributable;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.damage.DamageEffect;
|
||||
import org.bukkit.damage.DamageSource;
|
||||
import org.bukkit.damage.DamageType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
@Override
|
||||
public ComponentFlattener componentFlattener() {
|
||||
return ComponentFlattener.basic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component resolveWithContext(
|
||||
Component component,
|
||||
@Nullable CommandSender context,
|
||||
@Nullable Entity entity,
|
||||
boolean hasPermission
|
||||
) throws IOException {
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createEmptyStack() {
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleEventManager<Plugin> createPluginLifecycleEventManager(
|
||||
JavaPlugin plugin,
|
||||
BooleanSupplier registrationCheck
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatisticCriteriaKey(Statistic statistic) {
|
||||
return statistic.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Attributable getDefaultEntityAttributes(NamespacedKey key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDefaultEntityAttributes(NamespacedKey key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack deserializeItem(byte[] bytes) {
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslationKey(EntityType entityType) {
|
||||
return entityType.translationKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DamageEffect getDamageEffect(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PoiType.Occupancy createOccupancy(String type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Pose> validMannequinPoses() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MODERN, LEGACY> GameRule<LEGACY> legacyGameRuleBridge(
|
||||
GameRule<MODERN> modernRule,
|
||||
Function<LEGACY, MODERN> toModern,
|
||||
Function<MODERN, LEGACY> toLegacy,
|
||||
Class<LEGACY> legacyType
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component defaultMannequinDescription() {
|
||||
return Component.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinParts.Mutable allSkinParts() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvableProfile defaultMannequinProfile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<CommandSourceStack> restricted(Predicate<CommandSourceStack> predicate) {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombatEntry createCombatEntry(
|
||||
DamageSource damageSource,
|
||||
float damage,
|
||||
FallLocationType fallLocationType,
|
||||
float fallDistance
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombatEntry createCombatEntry(
|
||||
LivingEntity entity,
|
||||
DamageSource damageSource,
|
||||
float damage
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome constructLegacyCustomBiome() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -76,9 +76,10 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
|
||||
Class<?> jarClass = Class.forName(mainClass, true, classLoader);
|
||||
return (JavaPlugin) jarClass.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
throw new RuntimeException("Failed to instantiate plugin " + mainClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,23 +44,29 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
throws IllegalArgumentException {}
|
||||
|
||||
public void registerPlugin(@NotNull Plugin plugin) {
|
||||
plugins.put(plugin.getName().toLowerCase(), plugin);
|
||||
plugins.put(plugin.getName().toLowerCase(java.util.Locale.ENGLISH), plugin);
|
||||
for (String provided : plugin.getDescription().getProvides()) {
|
||||
plugins.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public @Nullable Plugin getPlugin(@NotNull String name) {
|
||||
return plugins.get(name.toLowerCase());
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
return plugins.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public @NotNull Plugin[] getPlugins() {
|
||||
return plugins.values().toArray(new Plugin[0]);
|
||||
return plugins.values().stream().distinct().toArray(Plugin[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPluginEnabled(@NotNull String name) {
|
||||
Plugin plugin = getPlugin(name);
|
||||
return plugin != null && plugin.isEnabled();
|
||||
return isPluginEnabled(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -152,6 +158,9 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
|
||||
@Override
|
||||
public void enablePlugin(@NotNull Plugin plugin) {
|
||||
if (plugin == null) {
|
||||
return;
|
||||
}
|
||||
if (!plugin.isEnabled()) {
|
||||
try {
|
||||
plugin.getPluginLoader().enablePlugin(plugin);
|
||||
@ -163,6 +172,9 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
|
||||
@Override
|
||||
public void disablePlugin(@NotNull Plugin plugin) {
|
||||
if (plugin == null) {
|
||||
return;
|
||||
}
|
||||
if (plugin.isEnabled()) {
|
||||
plugin.getPluginLoader().disablePlugin(plugin);
|
||||
}
|
||||
|
||||
@ -1589,10 +1589,7 @@ public class PatchBukkitServer implements Server {
|
||||
public <T extends Keyed> @Nullable Registry<T> getRegistry(
|
||||
@NotNull Class<T> tClass
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getRegistry'"
|
||||
);
|
||||
return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(tClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -59,8 +59,14 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
|
||||
@Override
|
||||
public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException {
|
||||
// Remove leading slash if present and split arguments
|
||||
String[] split = (cmdLine.startsWith("/") ? cmdLine.substring(1) : cmdLine).split(" ");
|
||||
if (cmdLine == null) return false;
|
||||
String cleanLine = cmdLine.trim();
|
||||
if (cleanLine.startsWith("/")) {
|
||||
cleanLine = cleanLine.substring(1).trim();
|
||||
}
|
||||
if (cleanLine.isEmpty()) return false;
|
||||
|
||||
String[] split = cleanLine.split("\\s+");
|
||||
if (split.length == 0) return false;
|
||||
|
||||
String sentLabel = split[0].toLowerCase();
|
||||
|
||||
@ -136,18 +136,14 @@ public class PatchBukkitCommandSender implements CommandSender, ServerOperator {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
if (this.server != null && this.server.getLogger() != null) {
|
||||
this.server.getLogger().info(message);
|
||||
} else {
|
||||
LogLevel logLevel = LogLevel.INFO;
|
||||
NativeBridgeFfi.sendLog(
|
||||
SendLogRequest.newBuilder()
|
||||
.setLevel(logLevel)
|
||||
.setMessage(ChatColor.stripColor(message))
|
||||
.setLoggerName("console")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
LogLevel logLevel = LogLevel.INFO;
|
||||
NativeBridgeFfi.sendLog(
|
||||
SendLogRequest.newBuilder()
|
||||
.setLevel(logLevel)
|
||||
.setMessage(ChatColor.stripColor(message))
|
||||
.setLoggerName("Console")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -10,6 +10,7 @@ import io.papermc.paper.registry.tag.TagKey;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.Sound;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
@ -26,11 +27,23 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
private final Map<NamespacedKey, B> entries = new LinkedHashMap<>();
|
||||
private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>();
|
||||
|
||||
private final RegistryKey<B> registryKey;
|
||||
|
||||
public PatchBukkitRegistry(
|
||||
RegistryType registryType,
|
||||
Function<GetRegistryDataResponse, List<P>> extractor,
|
||||
Function<P, B> factory
|
||||
) {
|
||||
this(registryType, extractor, factory, null);
|
||||
}
|
||||
|
||||
public PatchBukkitRegistry(
|
||||
RegistryType registryType,
|
||||
Function<GetRegistryDataResponse, List<P>> extractor,
|
||||
Function<P, B> factory,
|
||||
RegistryKey<B> registryKey
|
||||
) {
|
||||
this.registryKey = registryKey;
|
||||
if (registryType == null) return;
|
||||
|
||||
GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder()
|
||||
@ -49,39 +62,18 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void loadTags(JsonObject tagMap, RegistryKey<B> registryKey) {
|
||||
for (Map.Entry<String, JsonElement> entry : tagMap.entrySet()) {
|
||||
String tagName = entry.getKey();
|
||||
JsonArray members = entry.getValue().getAsJsonArray();
|
||||
|
||||
Set<NamespacedKey> memberKeys = new LinkedHashSet<>();
|
||||
for (JsonElement member : members) {
|
||||
String raw = member.getAsString();
|
||||
// Tag values may or may not have "minecraft:" prefix
|
||||
NamespacedKey key;
|
||||
if (raw.contains(":")) {
|
||||
key = NamespacedKey.fromString(raw);
|
||||
} else {
|
||||
key = new NamespacedKey(NamespacedKey.MINECRAFT, raw);
|
||||
}
|
||||
if (key != null) memberKeys.add(key);
|
||||
}
|
||||
|
||||
NamespacedKey tagKeyName = tagName.contains(":")
|
||||
? NamespacedKey.fromString(tagName)
|
||||
: new NamespacedKey(NamespacedKey.MINECRAFT, tagName);
|
||||
|
||||
if (tagKeyName == null) continue;
|
||||
|
||||
TagKey<B> tagKey = TagKey.create(registryKey, tagKeyName);
|
||||
tags.put(tagName, new PatchBukkitTag<>(tagKey, registryKey, memberKeys));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable B get(NamespacedKey key) {
|
||||
return entries.get(key);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
B value = entries.get(key);
|
||||
if (value == null && (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : ""))) {
|
||||
PatchBukkitSound dynamicSound = new PatchBukkitSound(key.getKey(), entries.size());
|
||||
entries.put(key, (B) dynamicSound);
|
||||
return (B) dynamicSound;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,7 +99,8 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
return new PatchBukkitRegistry<>(
|
||||
null,
|
||||
response -> Collections.emptyList(),
|
||||
obj -> null
|
||||
obj -> null,
|
||||
registryKey
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
|
||||
return (Registry<T>) instances.computeIfAbsent(registryKey, key -> {
|
||||
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(key);
|
||||
if (factoryEntry != null) {
|
||||
return buildRegistry(factoryEntry);
|
||||
return buildRegistry((RegistryKey) key, factoryEntry);
|
||||
}
|
||||
// Return an empty registry for unsupported types
|
||||
return PatchBukkitRegistry.empty((RegistryKey) key);
|
||||
@ -63,11 +63,12 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <P, B extends Keyed> PatchBukkitRegistry<P, B> buildRegistry(RegistryFactory<P, B> factory) {
|
||||
private static <P, B extends Keyed> PatchBukkitRegistry<P, B> buildRegistry(RegistryKey<B> key, RegistryFactory<P, B> factory) {
|
||||
return new PatchBukkitRegistry<>(
|
||||
factory.registryType(),
|
||||
factory.extractor(),
|
||||
factory.factory()
|
||||
factory.factory(),
|
||||
key
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
org.patchbukkit.PatchBukkitInternalAPIBridge
|
||||
4
rust/Cargo.lock
generated
4
rust/Cargo.lock
generated
@ -1800,9 +1800,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "j4rs"
|
||||
version = "0.24.0"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19df11b7e099dfa6c1d3d9ec600dc95d4130b24a2196181cbb7dfcd75936f4a9"
|
||||
checksum = "1fbaca95c5c7799ed193fc8aa789568f9d31a9d01381460227ef0074fea1855f"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"dunce",
|
||||
|
||||
@ -21,7 +21,7 @@ pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch
|
||||
tokio = "1"
|
||||
tracing = "0.1"
|
||||
glob = "0.3.4"
|
||||
j4rs = { version = "=0.24.0" }
|
||||
j4rs = { version = "=0.25.0" }
|
||||
rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] }
|
||||
walkdir = "2"
|
||||
zip = "8.6"
|
||||
@ -39,7 +39,7 @@ prost = "0.14"
|
||||
toml = "1.1"
|
||||
|
||||
[build-dependencies]
|
||||
j4rs = { version = "=0.24.0", artifact = "cdylib", lib = true }
|
||||
j4rs = { version = "=0.25.0", artifact = "cdylib", lib = true }
|
||||
env_logger = "0.11.11"
|
||||
log = "0.4"
|
||||
prost-build = "0.14"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -307,11 +307,37 @@ impl PluginManager {
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
for plugin in self.plugins.values_mut() {
|
||||
if plugin.instance.is_none() {
|
||||
if plugin.state != PluginState::Loaded {
|
||||
continue;
|
||||
}
|
||||
let plugin_instance = plugin.instance.as_ref().unwrap();
|
||||
let plugin_instance = jvm.clone_instance(plugin_instance).unwrap();
|
||||
let plugin_instance = match plugin.instance.as_ref() {
|
||||
Some(instance) => match jvm.clone_instance(instance) {
|
||||
Ok(cloned) => cloned,
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to clone plugin instance handle for {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Register plugin instance with Java PatchBukkitServer before enabling
|
||||
if let Ok(server) =
|
||||
jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())
|
||||
{
|
||||
let _ = jvm.invoke(
|
||||
&server,
|
||||
"registerPlugin",
|
||||
&[InvocationArg::from(
|
||||
jvm.clone_instance(&plugin_instance).unwrap(),
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
||||
let result = jvm.invoke(
|
||||
&plugin_manager,
|
||||
@ -344,11 +370,21 @@ impl PluginManager {
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
for plugin in self.plugins.values_mut() {
|
||||
if plugin.instance.is_none() {
|
||||
continue;
|
||||
}
|
||||
let plugin_instance = plugin.instance.as_ref().unwrap();
|
||||
let plugin_instance = jvm.clone_instance(plugin_instance).unwrap();
|
||||
let plugin_instance = match plugin.instance.as_ref() {
|
||||
Some(instance) => match jvm.clone_instance(instance) {
|
||||
Ok(cloned) => cloned,
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to clone plugin instance handle for {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let result = jvm.invoke(
|
||||
&plugin_manager,
|
||||
@ -396,7 +432,7 @@ impl PluginManager {
|
||||
Some(plugin) => plugin,
|
||||
None => continue,
|
||||
};
|
||||
let plugin_instance = jvm.invoke_static(
|
||||
let plugin_instance = match jvm.invoke_static(
|
||||
"org.patchbukkit.loader.PatchBukkitPluginLoader",
|
||||
"createPlugin",
|
||||
&[
|
||||
@ -405,7 +441,32 @@ impl PluginManager {
|
||||
InvocationArg::try_from(&classpath)?,
|
||||
InvocationArg::try_from(&libraries)?,
|
||||
],
|
||||
)?;
|
||||
) {
|
||||
Ok(instance) => {
|
||||
// Check if j4rs created a global reference for a null Java object
|
||||
if jvm
|
||||
.invoke(&instance, "toString", InvocationArg::empty())
|
||||
.is_err()
|
||||
{
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to instantiate PatchBukkit plugin {}: Java returned null instance",
|
||||
plugin.name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
instance
|
||||
}
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to instantiate PatchBukkit plugin {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
plugin.instance = Some(plugin_instance);
|
||||
for (cmd_name, cmd_data) in &plugin.commands {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user