Compare commits

...
5 changed files with 130 additions and 13 deletions

No files matched your search

@@ -9,8 +9,11 @@ import ac.grim.grimac.platform.bukkit.entity.BukkitGrimEntity;
import ac.grim.grimac.platform.bukkit.utils.anticheat.MultiLibUtil;
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.Vector3d;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.Getter;
@@ -27,6 +30,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPlayer {
private static final BukkitAudiences audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
@Getter
@@ -34,10 +38,18 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Getter
private final PlatformInventory inventory;
private final @Nullable User user;
public BukkitPlatformPlayer(Player bukkitPlayer) {
super(bukkitPlayer);
this.bukkitPlayer = bukkitPlayer;
this.inventory = new BukkitPlatformInventory(bukkitPlayer);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(bukkitPlayer.getUniqueId());
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
} else {
this.user = null;
}
}
@Override
@@ -67,12 +79,20 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Override
public void sendMessage(String message) {
bukkitPlayer.sendMessage(message);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
bukkitPlayer.sendMessage(message);
}
}
@Override
public void sendMessage(Component message) {
audiences.player(bukkitPlayer).sendMessage(message);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
audiences.player(bukkitPlayer).sendMessage(message);
}
}
@Override
@@ -54,22 +54,85 @@ public class MessageUtil {
return replacePlaceholders(player == null ? null : GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player.getUniqueId()), player, string, false);
}
private static final Pattern UNIFIED_PLACEHOLDER_PATTERN = Pattern.compile("%([a-zA-Z0-9_]+)%");
@Contract("_, _, null, _ -> null; _, _, !null, _ -> !null")
private @Nullable String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @Nullable String string, boolean removeFormatting) {
if (string == null) return null;
for (Map.Entry<String, String> entry : GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().entrySet()) {
string = string.replace(entry.getKey(), entry.getValue());
// --- OPTIMIZATION 1: THE FAST PATH ---
// If the string contains no '%' characters, it's impossible for it to have placeholders.
// indexOf() is a JVM intrinsic and is magnitudes faster than even creating a Matcher.
if (string.indexOf('%') == -1) {
// Since there are no % signs we can skip calling papi or our own replacement code
return string;
}
if (grimPlayer != null) {
for (Map.Entry<String, Function<GrimUser, String>> entry : GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().entrySet()) {
String value = entry.getValue().apply(grimPlayer).replace('%', PLACEHOLDER_ESCAPE_CHAR);
if (removeFormatting) value = filterDiscordText(value);
string = string.replace(entry.getKey(), value);
final Matcher matcher = UNIFIED_PLACEHOLDER_PATTERN.matcher(string);
// If matcher.find() is false, it means '%' existed but not in a valid %...% pattern.
// This avoids allocating a StringBuilder unless absolutely necessary.
if (!matcher.find()) {
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string);
}
// Get references to the maps once, outside the loop.
final Map<String, String> staticReplacements = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements();
final Map<String, Function<GrimUser, String>> variableReplacements = GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements();
final StringBuilder sb = new StringBuilder(string.length() + 32); // Pre-size with a little extra room
// --- OPTIMIZATION 2: THE UNIFIED SINGLE-PASS LOOP ---
// We use a do-while loop because we already performed the first matcher.find().
do {
// The full placeholder, e.g., "%tps%" or "%prefix%"
final String keyWithPercent = matcher.group(0);
String value = null;
// --- OPTIMIZATION 3: UNIFIED LOOKUP ---
// We check the static map first. This is a single, O(1) hash map lookup.
String staticValue = staticReplacements.get(keyWithPercent);
if (staticValue != null) {
value = staticValue;
} else if (grimPlayer != null) {
// If it's not a static placeholder, check if it's a dynamic one.
// This is a second, O(1) hash map lookup.
final Function<GrimUser, String> func = variableReplacements.get(keyWithPercent);
if (func != null) {
// LAZY EVALUATION: We only call the expensive function (like getTPS)
// if we actually found its placeholder in the string.
value = func.apply(grimPlayer);
}
}
}
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
// If we found no replacement, `value` will be null.
// In that case, we treat the placeholder as literal text by appending the original key.
if (value == null) {
value = keyWithPercent;
}
// --- OPTIMIZATION 4: CONDITIONAL FORMATTING ---
// The check for `removeFormatting` is inside the loop, but it's a simple boolean
// check and the cost is negligible compared to the string operations.
if (removeFormatting) {
// Note: This assumes `filterDiscordText` is reasonably fast.
// If it's slow, there are further micro-optimizations, but this is the right place for it.
value = filterDiscordText(value);
}
// `appendReplacement` efficiently appends the text between matches and our replacement value.
// `Matcher.quoteReplacement` should handle any '$' or '\' in the replacement value.
matcher.appendReplacement(sb, Matcher.quoteReplacement(value));
} while (matcher.find());
// Append the final part of the string after the last found placeholder.
matcher.appendTail(sb);
// Create the final string from our builder.
String grimReplaced = sb.toString();
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, grimReplaced).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
}
public static String filterDiscordText(String message) {
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -17,4 +17,16 @@ public class CommonGrimArguments {
public final static SystemArgument<String> PASTE_URL = FACTORY.create(string("PasteUrl", "https://paste.grim.ac/"));
public final static SystemArgument<Platform> PLATFORM_OVERRIDE = FACTORY.create(platform("PlatformOverride"));
/**
* Enables "Fast Bypass" mode for chat messages sent by GrimAC.
* <p>
* <b>BENEFIT:</b> Messages are sent directly as packets, significantly improving
* performance and reducing server overhead especially when lots of alerts are being sent.
* <p>
* <b>TRADE-OFF:</b> This completely bypasses the platform's event system (e.g., Bukkit's chat events).
* Other plugins will NOT be able to see, format, or cancel these messages.
* <p>
* This setting is opt-in (default: false) and requires a server restart to change.
*/
public final static SystemArgument<Boolean> USE_CHAT_FAST_BYPASS = FACTORY.create(string("ChatFastBypass", true));
}
@@ -1,28 +1,41 @@
package ac.grim.grimac.platform.fabric.player;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.entity.GrimEntity;
import ac.grim.grimac.platform.api.player.PlatformInventory;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import ac.grim.grimac.platform.fabric.entity.AbstractFabricGrimEntity;
import ac.grim.grimac.platform.fabric.utils.convert.FabricConversionUtil;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.Vector3d;
import net.kyori.adventure.text.Component;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
public abstract class AbstractFabricPlatformPlayer extends AbstractFabricGrimEntity implements PlatformPlayer {
protected ServerPlayerEntity fabricPlayer;
protected final AbstractFabricPlatformInventory inventory;
private final @Nullable User user;
public AbstractFabricPlatformPlayer(ServerPlayerEntity player) {
super(player);
this.fabricPlayer = player;
this.inventory = GrimACFabricLoaderPlugin.LOADER.getPlatformPlayerFactory().getPlatformInventory(player);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(fabricPlayer.getUuid());
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
} else {
this.user = null;
}
}
@Override
@@ -42,12 +55,20 @@ public abstract class AbstractFabricPlatformPlayer extends AbstractFabricGrimEnt
@Override
public void sendMessage(String message) {
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricMessageUtils().textLiteral(message), false);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricMessageUtils().textLiteral(message), false);
}
}
@Override
public void sendMessage(Component message) {
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricConversionUtil().toNativeText(message), false);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricConversionUtil().toNativeText(message), false);
}
}
@Override