Skip to content
This repository has been archived by the owner on May 27, 2024. It is now read-only.

Commit

Permalink
fix bad power getter
Browse files Browse the repository at this point in the history
  • Loading branch information
MrXiaoM committed Nov 4, 2023
1 parent d909a61 commit 0ea5548
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 102 deletions.
25 changes: 7 additions & 18 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ repositories {
jcenter()
mavenCentral()
maven { name 'NyaaCat'; url 'https://ci.nyaacat.com/maven/' }
maven { name 'enginehub'; url 'https://maven.enginehub.org/repo/' }
maven { name 'Aikar'; url 'https://repo.aikar.co/content/groups/aikar/' }
maven { name 'Spigot'; url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { name 'Sonatype'; url 'https://oss.sonatype.org/content/groups/public' }
maven { name "papermc"; url "https://papermc.io/repo/repository/maven-public/" }
maven { name 'vault-repo'; url 'https://jitpack.io' }
}
Expand All @@ -71,18 +71,14 @@ dependencies {
paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:$minecraftVersion-R0.1-SNAPSHOT")

compileOnly "cat.nyaa:nyaautils:8.0-SNAPSHOT"
shadow "cat.nyaa:nyaautils:8.0-SNAPSHOT"

compileOnly 'io.netty:netty-all:4.1.25.Final' // netty is shadowed inside spigot jar
compileOnly 'org.ow2.asm:asm:7.0'
compileOnly 'cglib:cglib-nodep:3.2.12'
shadow 'cglib:cglib-nodep:3.2.12'

api 'com.udojava:EvalEx:2.1'
shadow 'com.udojava:EvalEx:2.1'
api 'co.aikar:taskchain-bukkit:3.7.2'
shadow "co.aikar:taskchain-bukkit:3.7.2"
api 'commons-lang:commons-lang:2.6'
shadow 'commons-lang:commons-lang:2.6'
compileOnly 'org.ow2.asm:asm:9.3'
compileOnly 'net.bytebuddy:byte-buddy:1.12.16'
compileOnly 'com.udojava:EvalEx:2.7'
compileOnly 'commons-lang:commons-lang:2.6'

implementation('com.sk89q.worldguard:worldguard-core:7.0.4-SNAPSHOT')
implementation('com.sk89q.worldguard:worldguard-bukkit:7.0.4-SNAPSHOT') {
Expand All @@ -92,13 +88,9 @@ dependencies {
exclude group: 'org.spigotmc', module: 'spigot-api'
}
compileOnly('com.meowj:LangUtils:2.7-SNAPSHOT')
compileOnly('net.milkbowl.vault:VaultAPI:1.7') { transitive = false } // soft dep
// implementation fileTree(dir: 'libs', includes: ['*.jar'])
compileOnly('com.github.MilkBowl:VaultAPI:1.7') { transitive = false }

implementation 'com.google.code.findbugs:jsr305:1.3.9'
implementation('net.milkbowl.vault:VaultAPI:1.7') {
transitive = false
}
}

tasks.withType(ProcessResources).configureEach {
Expand All @@ -123,9 +115,6 @@ jar {
}

shadowJar {
archiveClassifier.set('release')
version = project.version
// relocate 'net.sf.cglib', 'cat.nyaa.cglib'
configurations = [project.configurations.shadow]
}

Expand Down
92 changes: 9 additions & 83 deletions src/main/java/think/rpgitems/item/RPGItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Block;
Expand Down Expand Up @@ -46,6 +43,7 @@
import think.rpgitems.power.marker.*;
import think.rpgitems.power.propertymodifier.Modifier;
import think.rpgitems.power.propertymodifier.RgiParameter;
import think.rpgitems.power.proxy.Interceptor;
import think.rpgitems.power.trigger.BaseTriggers;
import think.rpgitems.power.trigger.Trigger;
import think.rpgitems.utils.ColorHelper;
Expand Down Expand Up @@ -1662,21 +1660,14 @@ public BaseComponent getComponent(String locale) {

private <TEvent extends Event, T extends Pimpl, TResult, TReturn> List<T> getPower(Trigger<TEvent, T, TResult, TReturn> trigger, Player player, ItemStack stack) {
return powers.stream()
.filter(p -> p.getTriggers().contains(trigger))
.map(p -> {
Class<? extends Power> cls = p.getClass();
Power proxy = DynamicMethodInterceptor.create(p, player, cls, stack, trigger);
return PowerManager.createImpl(cls, proxy);
})
.map(p -> {
try {
return p.cast(trigger.getPowerClass());
} catch (ClassCastException ignored) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
.filter(p -> p.getTriggers().contains(trigger))
.map(p -> {
Class<? extends Power> cls = p.getClass();
Power proxy = Interceptor.create(p, player, stack, trigger);
return PowerManager.createImpl(cls, proxy);
})
.map(p -> p.cast(trigger.getPowerClass()))
.collect(Collectors.toList());
}

public PlaceholderHolder getPlaceholderHolder(String placeholderId) {
Expand Down Expand Up @@ -1845,71 +1836,6 @@ public List<String> getTemplatePlaceholders() {
return new ArrayList<>(templatePlaceholders);
}

@SuppressWarnings("rawtypes")
public static class DynamicMethodInterceptor implements MethodInterceptor {
private static WeakHashMap<Player, WeakHashMap<ItemStackWrapper, WeakHashMap<Power, Power>>> cache = new WeakHashMap<>();

private static Power makeProxy(Power orig, Player player, Class<? extends Power> cls, ItemStack stack, Trigger trigger) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setInterfaces(new Class[]{trigger.getPowerClass()});
enhancer.setCallback(new DynamicMethodInterceptor(orig, player, stack));
return (Power) enhancer.create();
}

protected static Power create(Power orig, Player player, Class<? extends Power> cls, ItemStack stack, Trigger trigger) {
return cache.computeIfAbsent(player, (k) -> new WeakHashMap<>())
.computeIfAbsent(ItemStackWrapper.of(stack), (p) -> new WeakHashMap<>())
.computeIfAbsent(orig, (s) -> makeProxy(orig, player, cls, stack, trigger));
}

private final Power orig;
private final Player player;
private final Map<Method, PropertyInstance> getters;
private ItemStack stack;

protected DynamicMethodInterceptor(Power orig, Player player, ItemStack stack) {
this.orig = orig;
this.player = player;
this.getters = PowerManager.getProperties(orig.getClass())
.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getValue().getKey(), e -> e.getValue().getValue()));
this.stack = stack;
}

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
if (getters.containsKey(method)) {
PropertyInstance propertyInstance = getters.get(method);
Class<?> type = propertyInstance.field().getType();
List<Modifier> playerModifiers = getModifiers(player);
List<Modifier> stackModifiers = getModifiers(stack);
List<Modifier> modifiers = Stream.concat(playerModifiers.stream(), stackModifiers.stream()).sorted(Comparator.comparing(Modifier::priority)).toList();
// Numeric modifiers
if (type == int.class || type == Integer.class || type == float.class || type == Float.class || type == double.class || type == Double.class) {

@SuppressWarnings("unchecked") List<Modifier<Double>> numberModifiers = modifiers.stream().filter(m -> (m.getModifierTargetType() == Double.class) && m.match(orig, propertyInstance)).map(m -> (Modifier<Double>) m).toList();
Number value = (Number) methodProxy.invoke(orig, args);
double origValue = value.doubleValue();
for (Modifier<Double> numberModifier : numberModifiers) {
RgiParameter param = new RgiParameter<>(orig.getItem(), orig, stack, origValue);
origValue = numberModifier.apply(param);
}
if (int.class.equals(type) || Integer.class.equals(type)) {
return (int) Math.round(origValue);
} else if (float.class.equals(type) || Float.class.equals(type)) {
return (float) (origValue);
} else {
return origValue;
}
}
}
return methodProxy.invoke(orig, args);
}
}

public void addTrigger(String name, Trigger trigger) {
triggers.put(name, trigger);
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/think/rpgitems/power/Power.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import think.rpgitems.power.trigger.Trigger;

import javax.annotation.Nullable;
import java.lang.invoke.MethodHandles;
import java.util.Collections;
import java.util.Locale;
import java.util.Objects;
Expand Down Expand Up @@ -133,4 +134,8 @@ static <T> Class<T> getUserClass(Class<T> clazz) {
}
return clazz;
}

default MethodHandles.Lookup getLookup() {
return MethodHandles.lookup();
}
}
174 changes: 174 additions & 0 deletions src/main/java/think/rpgitems/power/proxy/Interceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package think.rpgitems.power.proxy;

import cat.nyaa.nyaacore.Pair;
import cat.nyaa.nyaacore.utils.ItemTagUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import think.rpgitems.RPGItems;
import think.rpgitems.item.RPGItem;
import think.rpgitems.power.Power;
import think.rpgitems.power.PowerManager;
import think.rpgitems.power.PropertyInstance;
import think.rpgitems.power.propertymodifier.Modifier;
import think.rpgitems.power.propertymodifier.RgiParameter;
import think.rpgitems.power.trigger.Trigger;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static think.rpgitems.item.RPGItem.getModifiers;

public class Interceptor {
private static final Cache<String, Pair<origPowerHolder, Power>> POWER_CACHE = CacheBuilder.newBuilder().weakValues().build();
private final Power orig;
private final Player player;
private final Map<Method, PropertyInstance> getters;
private final ItemStack stack;
private final MethodHandles.Lookup lookup;

protected Interceptor(Power orig, Player player, ItemStack stack, MethodHandles.Lookup lookup) {
this.lookup = lookup;
this.orig = orig;
this.player = player;
this.getters = PowerManager.getProperties(orig.getClass())
.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getValue().getKey(), e -> e.getValue().getValue()));
this.stack = stack;
}

public static Power create(Power orig, Player player, ItemStack stack, Trigger trigger) {
Pair<origPowerHolder, Power> result = POWER_CACHE.getIfPresent(getCacheKey(player, stack, orig));
if (result != null) {
if (result.getKey().itemStack().equals(stack) && result.getKey().playerId().equals(player.getUniqueId()) && result.getKey().orig().equals(orig))
return result.getValue();
}
Power proxyPower = makeProxy(orig, player, stack, trigger);
POWER_CACHE.put(getCacheKey(player, stack, orig), Pair.of(new origPowerHolder(player.getUniqueId(), stack, orig), proxyPower));
return proxyPower;

}

private static Power makeProxy(Power orig, Player player, ItemStack stack, Trigger trigger) {
MethodHandles.Lookup lookup = orig.getLookup();
if (lookup == null) lookup = MethodHandles.lookup();
if (lookup.lookupClass() != orig.getClass()) {
try {
lookup = MethodHandles.privateLookupIn(orig.getClass(), lookup);
} catch (IllegalAccessException e) {
RPGItems.logger.severe("make proxy error: can not get lookup (is it outdated?): " + orig.getClass());
e.printStackTrace();
return orig;
}
}

MethodHandle constructorMH;

try {
Class<? extends Power> proxyClass = makeProxyClass(orig, player, stack, trigger, lookup);
constructorMH = lookup.findConstructor(proxyClass, MethodType.methodType(void.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
RPGItems.logger.severe("make proxy error: not instantiatable: " + orig.getClass());
e.printStackTrace();
return orig;
}

try {
return (Power) constructorMH.invoke();
} catch (Throwable e) {
RPGItems.logger.severe("make proxy error: not instantiatable (invoke error): " + orig.getClass());
e.printStackTrace();
return orig;
}
}

private static String getCacheKey(Player player, ItemStack itemStack, Power orig) {
String playerHash = player.getUniqueId().toString();
String itemHash = ItemTagUtils.getString(itemStack, RPGItem.NBT_ITEM_UUID).orElseGet(() -> String.valueOf(itemStack.hashCode()));
String origHash = orig.getName() + ":" + orig.getPlaceholderId() + ":" + orig.getClass().getName();
return playerHash + "-#-" + itemHash + "-#-" + origHash;

}

private static Class<? extends Power> makeProxyClass(Power orig, Player player, ItemStack stack, Trigger trigger, MethodHandles.Lookup lookup) throws NoSuchMethodException {
Class<? extends Power> origClass = orig.getClass();


return new ByteBuddy()
.subclass(origClass)
.implement(new Class[]{trigger.getPowerClass()})
.implement(NotUser.class)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(new Interceptor(orig, player, stack, lookup)))
.make()
.load(origClass.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(lookup))
.getLoaded();
}

@RuntimeType
@SuppressWarnings({"rawtypes", "unchecked"})
public Object intercept(@AllArguments Object[] args, @Origin Method method) {
try {
if (getters.containsKey(method)) {
PropertyInstance propertyInstance = getters.get(method);
Class<?> type = propertyInstance.field().getType();
List<Modifier> playerModifiers = getModifiers(player);
List<Modifier> stackModifiers = getModifiers(stack);
List<Modifier> modifiers = Stream.concat(playerModifiers.stream(), stackModifiers.stream()).sorted(Comparator.comparing(Modifier::priority)).toList();
// Numeric modifiers
if (type == int.class || type == Integer.class || type == float.class || type == Float.class || type == double.class || type == Double.class) {

List<Modifier<Double>> numberModifiers = modifiers.stream().filter(m -> (m.getModifierTargetType() == Double.class) && m.match(orig, propertyInstance)).map(m -> (Modifier<Double>) m).toList();
Number value = (Number) invokeMethod(method, orig, args);
double origValue = value.doubleValue();
for (Modifier<Double> numberModifier : numberModifiers) {
RgiParameter param = new RgiParameter<>(orig.getItem(), orig, stack, origValue);

origValue = numberModifier.apply(param);
}
if (int.class.equals(type) || Integer.class.equals(type)) {
return (int) Math.round(origValue);
} else if (float.class.equals(type) || Float.class.equals(type)) {
return (float) (origValue);
} else {
return origValue;
}
}
}

return invokeMethod(method, orig, args);
} catch (Throwable e) {
RPGItems.logger.severe("invoke method error:" + method);
e.printStackTrace();
}
return null;
}

private Object invokeMethod(Method method, Object obj, Object... args) throws Throwable {
//method.trySetAccessible();
MethodHandle MH;
MH = lookup.unreflect(method);
MH = MH.bindTo(obj);
return MH.invokeWithArguments(args);
}
}

record origPowerHolder(UUID playerId, ItemStack itemStack, Power orig) {
}
4 changes: 4 additions & 0 deletions src/main/java/think/rpgitems/power/proxy/NotUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package think.rpgitems.power.proxy;

public interface NotUser {
}
6 changes: 5 additions & 1 deletion src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ softdepend: [WorldGuard,NyaaUtils,Vault]
authors: [Taiterio,cyilin,RecursiveG,Librazy]
website: "https://github.com/MrXiaoM/RPGItems-reloaded"
database: false
api-version: 1.13
api-version: 1.19
libraries:
- 'com.udojava:EvalEx:2.7'
- 'commons-lang:commons-lang:2.6'
- 'net.bytebuddy:byte-buddy:LATEST'

commands:
rpgitem:
Expand Down

0 comments on commit 0ea5548

Please sign in to comment.