Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.1.0 Update #1

Merged
merged 7 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ bin/
# fabric

run/
/runClient/
/runServer/
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '1.0-SNAPSHOT'
id 'fabric-loom' version '1.3-SNAPSHOT'
id 'maven-publish'
}

Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
7 changes: 3 additions & 4 deletions minepkg.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@

# Preview of the minepkg.toml format! Could break anytime!
manifestVersion = 0

[package]
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"
author = "pkstDev"

# 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 = "*"
Expand Down
Binary file added screenshot/ping_me.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String> 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;
Expand All @@ -75,9 +86,10 @@ public void onRefresh(CallbackInfo ci) {
}
}
}
}
// }
}

@Unique
private int getLastPattern(String input, Pattern pattern){
if (Strings.isNullOrEmpty(input)) {
return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -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, Integer> 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<Matcher> matchers = PATTERN_COLOR_MAP.keySet().stream().map(
pattern -> pattern.matcher(finalLasting)
).collect(Collectors.toCollection(ArrayList::new));

Optional<Matcher> 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<Style> withStyle(String ping, int color) {
// Remove @ symbol
String name = ping.substring(1);

UnaryOperator<Style> baseStyle =
style -> style.withColor(color)
.withItalic(true)
.withClickEvent(new ClickEvent(
ClickEvent.Action.SUGGEST_COMMAND,
ping + " "
));

// 'everyone'
if (name.equals("everyone")) {
return style -> baseStyle.apply(style)
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
Text.translatable("action.pingme.ping_everyone")
.styled(s -> s.withColor(color))
));
}

// '...'
else {
AtomicReference<UnaryOperator<Style>> result = new AtomicReference<>(style -> style);

if (MinecraftClient.getInstance().getServer() != null) {
MinecraftClient.getInstance().getServer().getPlayerManager().getPlayerList()
.stream().filter(player -> Objects.equals(player.getName().getString(), name)).findFirst().ifPresent(player -> result.set(
style -> baseStyle.apply(style)
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
MutableText.of(Text.empty().getContent())
.append(
MutableText.of(player.getDisplayName().getContent())
.styled(s -> s.withColor(color))
).append("\n")
.append(Text.literal(player.getUuidAsString()))
))
));
}

return result.get();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package dev.intelligentcreations.pingme.mixin;

import fr.catcore.server.translations.api.LocalizationTarget;
import fr.catcore.server.translations.api.ServerTranslations;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SentMessage;
import net.minecraft.server.MinecraftServer;
Expand All @@ -12,9 +10,14 @@
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;
import xyz.nucleoid.server.translations.api.LocalizationTarget;
import xyz.nucleoid.server.translations.impl.ServerTranslations;

import java.util.Objects;

@Mixin(ServerPlayerEntity.class)
public abstract class ServerPlayerEntityMixin {
Expand All @@ -28,35 +31,51 @@ public abstract class ServerPlayerEntityMixin {
)
)
public void onSendMessage(SentMessage message, boolean filterMaskEnabled, MessageType.Parameters params, CallbackInfo ci) {
if (!this.server.isSingleplayer()) {
String processString = message.getContent().getString();
while (processString.contains("@")) {
processString = processString.substring(processString.indexOf("@"));
// 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.server.isSingleplayer()) {
String processedString = message.getContent().getString();

while (processedString.contains("@")) {
processedString = processedString.substring(processedString.indexOf("@"));
ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(params.name().getString());

if (player != null) {
if (processString.contains(" ")) {
String name = processString.substring(processString.indexOf("@") + 1, processString.indexOf(" "));
doPing(player, name);
} else {
String name = processString.substring(processString.indexOf("@") + 1);
doPing(player, name);
}
}
processString = processString.substring(1);
String name;

if (processedString.contains(" ")) name = processedString.substring(processedString.indexOf("@") + 1, processedString.indexOf(" "));
else name = processedString.substring(processedString.indexOf("@") + 1);

doPing(player, name);
}
processedString = processedString.substring(1);
}
}


// }
}

@Unique
private void doPing(ServerPlayerEntity playerEntity, String name) {
Text pingMessage = Text.literal(playerEntity.getName().getString() + ServerTranslations.INSTANCE.getLanguage((LocalizationTarget) playerEntity).remote().get("message.pingme.ping"));
Text pingMessage = Text.literal(
playerEntity.getName().getString()
+ ServerTranslations.INSTANCE.getLanguage((LocalizationTarget) playerEntity)
.serverTranslations()
.get("message.pingme.ping")
);

if (name.equals("everyone")) {
this.server.getPlayerManager().getPlayerList().forEach(player -> {
if (player.getName() != playerEntity.getName()) {
if (!Objects.equals(player.getName().getString(), playerEntity.getName().getString())) {
System.out.println(player.getName());
player.sendMessage(pingMessage, true);
player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.MASTER, 1.0f, 1.0f);
}
});
} else {
}

else {
ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(name);
if (player != null) {
player.sendMessage(pingMessage, true);
Expand Down
5 changes: 3 additions & 2 deletions src/main/resources/data/pingme/lang/en_us.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"message.pingme.ping": " just mentioned you in the chat!"
}
"message.pingme.ping": " just mentioned you in the chat!",
"action.pingme.ping_everyone": "Ping everyone in a new chat"
}
4 changes: 4 additions & 0 deletions src/main/resources/data/pingme/lang/ja_jp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"message.pingme.ping": "がチャットであなたのことを話しました!",
"action.pingme.ping_everyone": "新しいチャットで全員に言及"
}
5 changes: 3 additions & 2 deletions src/main/resources/data/pingme/lang/zh_cn.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"message.pingme.ping": " 刚刚在聊天里提及了你!"
}
"message.pingme.ping": " 刚刚在聊天里提及了你!",
"action.pingme.ping_everyone": "在新的聊天里提及所有人"
}
Loading
Loading