Skip to content

Commit

Permalink
Allow plugins to register "friend adders"
Browse files Browse the repository at this point in the history
  • Loading branch information
Gaming32 committed Jul 15, 2024
1 parent d8d21e3 commit a19a38c
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 108 deletions.
9 changes: 9 additions & 0 deletions src/main/java/io/github/gaming32/worldhost/WorldHost.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.github.gaming32.worldhost.config.WorldHostConfig;
import io.github.gaming32.worldhost.gui.OnlineStatusLocation;
import io.github.gaming32.worldhost.gui.screen.JoiningWorldHostScreen;
import io.github.gaming32.worldhost.plugin.FriendAdder;
import io.github.gaming32.worldhost.plugin.InfoTextsCategory;
import io.github.gaming32.worldhost.plugin.OnlineFriend;
import io.github.gaming32.worldhost.plugin.WorldHostPlugin;
Expand Down Expand Up @@ -417,6 +418,14 @@ public static List<Component> getInfoTexts(InfoTextsCategory category) {
return result;
}

public static List<FriendAdder> getFriendAdders() {
return plugins.stream()
.map(LoadedWorldHostPlugin::plugin)
.map(WorldHostPlugin::friendAdder)
.flatMap(Optional::stream)
.toList();
}

public static void tickHandler() {
if (protoClient == null || protoClient.isClosed()) {
protoClient = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.github.gaming32.worldhost.gui.screen;

import com.mojang.authlib.GameProfile;
import com.mojang.blaze3d.systems.RenderSystem;
import io.github.gaming32.worldhost.WorldHost;
import io.github.gaming32.worldhost.gui.widget.FriendAdderSelectorButton;
import io.github.gaming32.worldhost.plugin.FriendAdder;
import io.github.gaming32.worldhost.plugin.FriendListFriend;
import io.github.gaming32.worldhost.plugin.ProfileInfo;
import io.github.gaming32.worldhost.versions.Components;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
Expand All @@ -12,14 +15,12 @@
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.players.GameProfileCache;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;

import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;

//#if MC >= 1.20.0
import net.minecraft.client.gui.GuiGraphics;
Expand All @@ -28,94 +29,135 @@
//#endif

public class AddFriendScreen extends WorldHostScreen {
public static final Pattern VALID_USERNAME = Pattern.compile("^[a-zA-Z0-9_]{1,16}$");
public static final Pattern VALID_UUID = Pattern.compile("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$");
private static final Component FRIEND_USERNAME_TEXT = Components.translatable("world-host.add_friend.enter_username");

private final Screen parent;
private final Consumer<GameProfile> addAction;
private final Consumer<FriendListFriend> addAction;

private final List<FriendAdder> friendAdders = WorldHost.getFriendAdders();
private FriendAdder friendAdder = friendAdders.getFirst();

private Consumer<String> usernameResponder;
private Button addFriendButton;
private EditBox usernameField;
private EditBox nameField;
private long lastTyping;
private boolean usernameUpdate;
private GameProfile friendProfile;
private boolean delayedFriendUpdate;

private FriendListFriend friend;
private int reloadCount;
private ProfileInfo friendProfile;

public AddFriendScreen(Screen parent, Component title, UUID prefilledUser, Consumer<GameProfile> addAction) {
public AddFriendScreen(
Screen parent,
Component title,
@Nullable FriendListFriend prefilledFriend,
Consumer<FriendListFriend> addAction
) {
super(title);
this.parent = parent;
this.addAction = addAction;
if (prefilledUser != null) {
friendProfile = WorldHost.fetchProfile(Minecraft.getInstance().getMinecraftSessionService(), prefilledUser);
if (prefilledFriend != null) {
setFriend(prefilledFriend);
}
}

private void setFriend(FriendListFriend friend) {
this.friend = friend;
addFriendButton.active = friend != null;
final int currentReloadCount = ++reloadCount;
if (friend == null) {
friendProfile = null;
return;
}
friendProfile = friend.fallbackProfileInfo();
friend.profileInfo()
.thenAcceptAsync(ready -> {
if (reloadCount == currentReloadCount) {
friendProfile = ready;
}
}, Minecraft.getInstance())
.exceptionally(t -> {
WorldHost.LOGGER.error("Failed to request profile info for {}", friend, t);
return null;
});
}

private void resolveFriend(String name) {
final int currentReloadCount = ++reloadCount;
friendAdder.resolveFriend(name)
.thenAcceptAsync(ready -> {
if (reloadCount == currentReloadCount) {
setFriend(ready.orElse(null));
}
}, Minecraft.getInstance())
.exceptionally(t -> {
WorldHost.LOGGER.error("Failed to request friend with name '{}'", name, t);
return null;
});
}

@Override
protected void init() {
assert minecraft != null;
sendRepeatEvents(true);
GameProfileCache.setUsesAuthentication(true); // This makes non-existent users return an empty value instead of an offline mode fallback.

nameField = addRenderableWidget(new EditBox(font, width / 2 - 100, 66, 200, 20, nameField, FRIEND_USERNAME_TEXT));
nameField.setMaxLength(36);
//#if MC >= 1.19.4
setInitialFocus(nameField);
//#else
//$$ usernameField.setFocus(true);
//#endif
nameField.setResponder(name -> {
lastTyping = Util.getMillis();
setFriend(null);
addFriendButton.active = false;
if (friendAdder.rateLimit(name)) {
delayedFriendUpdate = true;
} else {
delayedFriendUpdate = false;
resolveFriend(name);
}
});

int extraY = 0;
if (showFriendAddersButton()) {
addRenderableWidget(new FriendAdderSelectorButton(
width / 2 - 100, 90, 200, 20,
Components.translatable("world-host.add_friend.friend_adder"),
button -> {
friendAdder = button.getValue();
setFriend(null);
addFriendButton.active = false;
resolveFriend(nameField.getValue());
},
friendAdders.toArray(FriendAdder[]::new)
));
extraY += 24;
}

addFriendButton = addRenderableWidget(
button(Components.translatable("world-host.add_friend"), button -> {
if (friendProfile != null) { // Just in case the user somehow clicks the button with this null
addAction.accept(friendProfile);
if (friend != null) { // Just in case the user somehow clicks the button with this null
addAction.accept(friend);
}
minecraft.setScreen(parent);
}).pos(width / 2 - 100, height / 4 + 108)
}).pos(width / 2 - 100, height / 4 + 108 + extraY)
.width(200)
.build()
);
addFriendButton.active = friendProfile != null;
addFriendButton.active = friend != null;

addRenderableWidget(
button(CommonComponents.GUI_CANCEL, button -> minecraft.setScreen(parent))
.pos(width / 2 - 100, height / 4 + 132)
.pos(width / 2 - 100, height / 4 + 132 + extraY)
.width(200)
.build()
);

usernameField = addWidget(new EditBox(font, width / 2 - 100, 66, 200, 20, FRIEND_USERNAME_TEXT));
usernameField.setMaxLength(36);
//#if MC >= 1.19.4
setInitialFocus(usernameField);
//#else
//$$ usernameField.setFocus(true);
//#endif
if (friendProfile != null) {
usernameField.setValue(StringUtils.getIfBlank(friendProfile.getName(), () -> friendProfile.getId().toString()));
}
if (usernameResponder == null) {
// Only set the responder here on first init
usernameField.setResponder(usernameResponder = username -> {
lastTyping = Util.getMillis();
if (VALID_USERNAME.matcher(username).matches()) {
usernameUpdate = true;
friendProfile = null;
addFriendButton.active = false;
} else if (VALID_UUID.matcher(username).matches()) {
usernameUpdate = false;
friendProfile = new GameProfile(UUID.fromString(username), "");
addFriendButton.active = true;
} else if (username.startsWith("o:")) {
usernameUpdate = false;
final String actualName = username.substring(2);
// TODO: Use createOfflinePlayerUUID when 1.19.2+ becomes the minimum, and createOfflineProfile in 1.20.4+
friendProfile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + actualName).getBytes(StandardCharsets.UTF_8)), actualName);
addFriendButton.active = true;
}
});
}
}

@Override
public void resize(@NotNull Minecraft minecraft, int width, int height) {
final String oldUsername = usernameField.getValue();
super.resize(minecraft, width, height);
usernameField.setValue(oldUsername);
// Make sure to set the responder *after* the value
usernameField.setResponder(usernameResponder);
private boolean showFriendAddersButton() {
return friendAdders.size() > 1;
}

@Override
Expand All @@ -133,8 +175,8 @@ public void removed() {
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (
addFriendButton.active &&
getFocused() == usernameField &&
(keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER)
getFocused() == nameField &&
(keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER)
) {
addFriendButton.onPress();
return true;
Expand All @@ -147,18 +189,9 @@ public void tick() {
//#if MC < 1.20.2
//$$ usernameField.tick();
//#endif
if (Util.getMillis() - 300 > lastTyping && usernameUpdate) {
usernameUpdate = false;
final String username = usernameField.getValue();
WorldHost.getMaybeAsync(WorldHost.getProfileCache(), username, p -> {
if (p.isPresent()) {
assert minecraft != null;
friendProfile = WorldHost.fetchProfile(minecraft.getMinecraftSessionService(), p.get());
addFriendButton.active = true;
} else {
friendProfile = null;
}
});
if (Util.getMillis() - 300 > lastTyping && delayedFriendUpdate) {
delayedFriendUpdate = false;
resolveFriend(nameField.getValue());
}
}

Expand All @@ -177,23 +210,20 @@ public void render(
whRenderBackground(context, mouseX, mouseY, delta);
drawCenteredString(context, font, title, width / 2, 20, 0xffffff);
drawString(context, font, FRIEND_USERNAME_TEXT, width / 2 - 100, 50, labelColor);
usernameField.render(context, mouseX, mouseY, delta);
super.render(context, mouseX, mouseY, delta);

if (friendProfile != null) {
assert minecraft != null;
final var skinTexture = WorldHost.getSkinLocationNow(friendProfile);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
RenderSystem.enableBlend();
final int yShift = showFriendAddersButton() ? 24 : 0;
//#if MC >= 1.19.4
final int addFriendY = addFriendButton.getY();
//#else
//$$ final int addFriendY = addFriendButton.y;
//#endif
final int size = addFriendY - 110;
final int size = addFriendY - 110 - yShift;
final int x = width / 2 - size / 2;
blit(context, skinTexture, x, 98, size, size, 8, 8, 8, 8, 64, 64);
blit(context, skinTexture, x, 98, size, size, 40, 8, 8, 8, 64, 64);
friendProfile.iconRenderer().draw(context, x, 98 + yShift, size, size);
RenderSystem.disableBlend();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package io.github.gaming32.worldhost.gui.screen;

import com.mojang.authlib.GameProfile;
import com.mojang.blaze3d.systems.RenderSystem;
import io.github.gaming32.worldhost.WorldHost;
import io.github.gaming32.worldhost.WorldHostComponents;
import io.github.gaming32.worldhost.plugin.FriendListFriend;
import io.github.gaming32.worldhost.plugin.InfoTextsCategory;
import io.github.gaming32.worldhost.plugin.ProfileInfo;
import io.github.gaming32.worldhost.plugin.vanilla.WorldHostFriendListFriend;
import io.github.gaming32.worldhost.versions.Components;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
Expand All @@ -33,6 +31,11 @@ public class FriendsScreen extends ScreenWithInfoTexts {
private Button removeButton;
private FriendsList list;

private final Runnable refresher = () -> {
assert minecraft != null;
minecraft.execute(() -> list.reloadEntries());
};

public FriendsScreen(Screen parent) {
super(WorldHostComponents.FRIENDS, InfoTextsCategory.FRIENDS_SCREEN);
this.parent = parent;
Expand All @@ -56,12 +59,10 @@ protected void init() {
addRenderableWidget(
button(ADD_FRIEND_TEXT, button -> {
assert minecraft != null;
minecraft.setScreen(new AddFriendScreen(this, ADD_FRIEND_TEXT, null, profile -> {
addFriendAndUpdate(profile);
if (WorldHost.protoClient != null) {
WorldHost.protoClient.friendRequest(profile.getId());
}
}));
minecraft.setScreen(new AddFriendScreen(
this, ADD_FRIEND_TEXT, null,
friend -> friend.addFriend(true, refresher)
));
}).width(152)
.pos(width / 2 - 154, height - 54)
.tooltip(Components.translatable("world-host.add_friend.tooltip"))
Expand All @@ -71,7 +72,10 @@ protected void init() {
addRenderableWidget(
button(ADD_SILENTLY_TEXT, button -> {
assert minecraft != null;
minecraft.setScreen(new AddFriendScreen(this, ADD_SILENTLY_TEXT, null, this::addFriendAndUpdate));
minecraft.setScreen(new AddFriendScreen(
this, ADD_SILENTLY_TEXT, null,
friend -> friend.addFriend(false, refresher)
));
}).width(152)
.pos(width / 2 - 154, height - 30)
.tooltip(Components.translatable("world-host.friends.add_silently.tooltip"))
Expand Down Expand Up @@ -107,15 +111,6 @@ public void onClose() {
minecraft.setScreen(parent);
}

private void addFriendAndUpdate(GameProfile profile) {
addFriend(profile);
list.addEntry(new FriendsEntry(new WorldHostFriendListFriend(profile)));
}

public static void addFriend(GameProfile profile) {
WorldHost.addFriends(profile.getId());
}

@Override
public void render(
@NotNull
Expand Down Expand Up @@ -183,10 +178,10 @@ public FriendsEntry(FriendListFriend friend) {
friend.profileInfo()
.thenAccept(ready -> profile = ready)
.exceptionally(t -> {
WorldHost.LOGGER.error("Failed to request profile skin for {}", friend, t);
WorldHost.LOGGER.error("Failed to request profile info for {}", friend, t);
return null;
});
}
}

@NotNull
@Override
Expand Down Expand Up @@ -224,7 +219,7 @@ public void maybeRemove() {
minecraft.setScreen(new ConfirmScreen(
yes -> {
if (yes) {
friend.removeFriend(() -> minecraft.execute(() -> FriendsScreen.this.list.reloadEntries()));
friend.removeFriend(refresher);
}
minecraft.setScreen(FriendsScreen.this);
},
Expand Down
Loading

0 comments on commit a19a38c

Please sign in to comment.