diff --git a/.gitignore b/.gitignore index 09cd281..2e46a52 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ bin/ # fabric run/ +/runClient/ +/runServer/ diff --git a/README.md b/README.md index 22b50ea..95e75c1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # Ping Me -A server-side Fabric mod that allows you to ping anyone from the chat with notifications! +A server-side Fabric mod allowing you to ping anybody from the chat with action bar notifications! -Implemented features: -* Server-side ping functionality, with "@everyone" supported (Vanilla client compatible) -* Client-side name auto-completion (If the client has also installed the mod) +You can also install the mod client-side for better experiences. -Planned features: -* Highlight the ping message +Features: +- [X] `(Vanilla Client Compatible)` Server-side ping functionality, with `@everyone` supported +- [X] `(Vanilla Client Compatible)` Action bar notification with a pleasant sound +- [X] `(Modded Client Required)` Client-side player name auto-completion +- [X] `(Modded Client Required)` Highlighted pings + +Screenshots: + +![Ping Me!](screenshot/ping_me.png) diff --git a/build.gradle b/build.gradle index 1ca2a14..1a479cf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.0-SNAPSHOT' + id 'fabric-loom' version '1.3-SNAPSHOT' id 'maven-publish' } @@ -43,7 +43,7 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modRuntimeOnly "maven.modrinth:mixintrace:1.1.1+1.17" - modImplementation include("fr.catcore:server-translations-api:1.4.19+1.19.3") + modImplementation include("xyz.nucleoid:server-translations-api:2.0.0+1.20") } processResources { diff --git a/gradle.properties b/gradle.properties index 0659961..313e118 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,14 +3,14 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/develop - minecraft_version=1.19.3 - yarn_mappings=1.19.3+build.5 - loader_version=0.14.12 + minecraft_version=1.20 + yarn_mappings=1.20+build.1 + loader_version=0.14.22 # Mod Properties - mod_version = 1.0.0 + mod_version = 1.1.0 maven_group = dev.intelligentcreations archives_base_name = PingMe-fabric # Dependencies - fabric_version=0.72.0+1.19.3 + fabric_version=0.83.0+1.20 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/minepkg.toml b/minepkg.toml index 2bff8ae..d5238dd 100644 --- a/minepkg.toml +++ b/minepkg.toml @@ -1,4 +1,3 @@ - # Preview of the minepkg.toml format! Could break anytime! manifestVersion = 0 @@ -6,7 +5,7 @@ manifestVersion = 0 type = "mod" name = "ping-me" description = "Ping anyone from the chat with notifications!" - version = "1.0.0+mc1.19.3" + version = "1.1.0+mc1.19.3" platform = "fabric" license = "MIT" source = "https://github.com/IntelligentCreations/PingMe" @@ -14,8 +13,8 @@ manifestVersion = 0 # These are global requirements [requirements] - minecraft = "~1.19.3" - fabricLoader = ">=0.14.6" + minecraft = ["~1.20", "~1.20.1"] + fabricLoader = ">=0.14.22" [dependencies] fabric = "*" diff --git a/screenshot/ping_me.png b/screenshot/ping_me.png new file mode 100644 index 0000000..a31dfca Binary files /dev/null and b/screenshot/ping_me.png differ diff --git a/src/main/java/dev/intelligentcreations/pingme/mixin/ChatInputSuggestorMixin.java b/src/main/java/dev/intelligentcreations/pingme/mixin/ChatInputSuggestorMixin.java index 0c9e23a..4fb474f 100644 --- a/src/main/java/dev/intelligentcreations/pingme/mixin/ChatInputSuggestorMixin.java +++ b/src/main/java/dev/intelligentcreations/pingme/mixin/ChatInputSuggestorMixin.java @@ -13,6 +13,7 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -25,7 +26,9 @@ @Mixin(ChatInputSuggestor.class) public abstract class ChatInputSuggestorMixin { + @Unique private static final Pattern COLON_PATTERN = Pattern.compile("(@)"); + @Unique private static final Pattern WHITESPACE_PATTERN = Pattern.compile("(\\s+)"); @Shadow @Final TextFieldWidget textField; @@ -45,26 +48,34 @@ public abstract class ChatInputSuggestorMixin { cancellable = true ) public void onRefresh(CallbackInfo ci) { - if (!this.client.isInSingleplayer()) { + // We'd better not judge if the server is single-player or not, + // otherwise LAN world servers won't be able to enjoy the mod. + + // if (!this.client.isInSingleplayer()) { String message = this.textField.getText(); StringReader reader = new StringReader(message); boolean hasSlash = reader.canRead() && reader.peek() == '/'; if (hasSlash) reader.skip(); + boolean isCommand = this.slashOptional || hasSlash; int cursor = this.textField.getCursor(); + if (!isCommand) { String textUptoCursor = message.substring(0, cursor); int start = Math.max(getLastPattern(textUptoCursor, COLON_PATTERN) - 1, 0); int whitespace = getLastPattern(textUptoCursor, WHITESPACE_PATTERN); + if (start < textUptoCursor.length() && start >= whitespace) { if (textUptoCursor.charAt(start) == '@') { List playerNames = new ArrayList<>(); ClientPlayNetworkHandler networkHandler = this.client.getNetworkHandler(); + if (networkHandler != null) { playerNames.add("@everyone"); networkHandler.getPlayerList().forEach(entry -> playerNames.add("@" + entry.getProfile().getName()) ); + this.pendingSuggestions = CommandSource.suggestMatching(playerNames, new SuggestionsBuilder(textUptoCursor, start)); this.pendingSuggestions.thenRun(() -> { if (this.pendingSuggestions.isDone()) return; @@ -75,9 +86,10 @@ public void onRefresh(CallbackInfo ci) { } } } - } + // } } + @Unique private int getLastPattern(String input, Pattern pattern){ if (Strings.isNullOrEmpty(input)) { return 0; diff --git a/src/main/java/dev/intelligentcreations/pingme/mixin/MessageHandlerMixin.java b/src/main/java/dev/intelligentcreations/pingme/mixin/MessageHandlerMixin.java new file mode 100644 index 0000000..29b117b --- /dev/null +++ b/src/main/java/dev/intelligentcreations/pingme/mixin/MessageHandlerMixin.java @@ -0,0 +1,121 @@ +package dev.intelligentcreations.pingme.mixin; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.message.MessageHandler; +import net.minecraft.text.*; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Mixin(MessageHandler.class) +public class MessageHandlerMixin { + @Unique + private static final HashMap PATTERN_COLOR_MAP = new HashMap<>(ImmutableMap.of( + // @everyone + Pattern.compile("@everyone(?![A-Za-z0-9_.-])"), 0xFFCB25, + // @... + Pattern.compile("@(?!everyone)[A-Za-z0-9_.-]+"), 0xFF52D0 + )); + + @ModifyArg( + method = "processChatMessageInternal", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V" + ) + ) + private Text colorPings(Text message) { + return matchAndColor(message.getString()); + } + + @Unique + private MutableText matchAndColor(String string) { + String lasting = string; + MutableText text = Text.empty(); + + while (!lasting.isEmpty()) { + String finalLasting = lasting; + ArrayList matchers = PATTERN_COLOR_MAP.keySet().stream().map( + pattern -> pattern.matcher(finalLasting) + ).collect(Collectors.toCollection(ArrayList::new)); + + Optional currentMatcher = matchers.stream().filter(Matcher::find).findFirst(); + if (currentMatcher.isPresent()) { + int start = currentMatcher.get().start(), end = currentMatcher.get().end(); + int color = PATTERN_COLOR_MAP.get(currentMatcher.get().pattern()); + + String betweenMatches = lasting.substring(0, start); + String currentMatch = lasting.substring(start, end); + + text.append(Text.literal(betweenMatches)); + text.append(Text.literal(currentMatch).styled(withStyle(currentMatch, color))); + + lasting = lasting.substring(end); + } + else { + break; + } + } + + return text.append(Text.literal(lasting)); + } + + @Unique + private UnaryOperator