├── .github └── workflows │ ├── gradle.yml │ └── release.yml ├── .gitignore ├── FriendAPI-12 ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── utilitymods │ │ └── friendapi │ │ ├── ForgeProfileFactory.java │ │ └── FriendMod.java │ └── resources │ └── mcmod.info ├── FriendAPI-16 ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── utilitymods │ │ └── friendapi │ │ ├── FabricProfileFactory.java │ │ ├── FriendMod.java │ │ └── command │ │ ├── AffinityArgument.java │ │ ├── Commands.java │ │ ├── FriendArgument.java │ │ └── PlayerArgument.java │ └── resources │ └── fabric.mod.json ├── FriendAPI-17 ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── utilitymods │ │ └── friendapi │ │ ├── FabricProfileFactory.java │ │ ├── FriendMod.java │ │ └── command │ │ ├── AffinityArgument.java │ │ ├── Commands.java │ │ ├── FriendArgument.java │ │ └── PlayerArgument.java │ └── resources │ └── fabric.mod.json ├── FriendAPI-18 ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── utilitymods │ │ └── friendapi │ │ ├── FabricProfileFactory.java │ │ ├── FriendMod.java │ │ └── command │ │ ├── AffinityArgument.java │ │ ├── Commands.java │ │ ├── FriendArgument.java │ │ └── PlayerArgument.java │ └── resources │ └── fabric.mod.json ├── FriendAPI-19 ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── utilitymods │ │ └── friendapi │ │ ├── FabricProfileFactory.java │ │ ├── FriendMod.java │ │ └── command │ │ ├── AffinityArgument.java │ │ ├── Commands.java │ │ ├── FriendArgument.java │ │ └── PlayerArgument.java │ └── resources │ └── fabric.mod.json ├── FriendAPI ├── build.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── utilitymods │ │ │ └── friendapi │ │ │ ├── FriendManager.java │ │ │ ├── exceptions │ │ │ └── ApiFailedException.java │ │ │ ├── profiles │ │ │ ├── Affinity.java │ │ │ ├── Profile.java │ │ │ └── ProfileFactory.java │ │ │ └── serialization │ │ │ └── MapAdapter.java │ └── resources │ │ └── assets │ │ └── friendapi │ │ └── lang │ │ └── en_us.json │ └── test │ └── java │ └── org │ └── utilitymods │ └── friendapi │ ├── FriendManagerTest.java │ ├── Main.java │ ├── MigrationTest.java │ ├── ProfileData.java │ └── Reflection.java ├── LICENSE ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | paths: 9 | - '**/*src/**' 10 | 11 | pull_request: 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v2 22 | with: 23 | java-version: '17' 24 | distribution: 'zulu' 25 | cache: gradle 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | - name: Build with Gradle 29 | run: ./gradlew build 30 | - name: Publish to maven 31 | if: ${{ success() && github.event_name != 'pull_request' }} 32 | run: ./gradlew publishAll 33 | env: 34 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 35 | MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle 3 | 4 | name: Release 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '17' 21 | distribution: 'zulu' 22 | cache: gradle 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build -PisRelease=true 27 | - name: Publish to maven 28 | if: ${{ success() }} 29 | run: ./gradlew publishAll -PisRelease=true 30 | env: 31 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 32 | MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 33 | GH_USERNAME: ${{ github.actor }} 34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /FriendAPI-12/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { 4 | name = 'forge' 5 | url = 'https://files.minecraftforge.net/maven' 6 | } 7 | } 8 | dependencies { 9 | classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.+', changing: true 10 | } 11 | } 12 | 13 | apply plugin: 'net.minecraftforge.gradle' 14 | 15 | archivesBaseName = "FriendAPI-12" 16 | 17 | 18 | minecraft { 19 | mappings channel: 'stable', version: '39-1.12' 20 | } 21 | 22 | dependencies { 23 | 24 | minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855' 25 | api project(':FriendAPI') 26 | library(project(':FriendAPI')) { 27 | transitive false 28 | } 29 | 30 | 31 | } 32 | 33 | java { 34 | toolchain { 35 | languageVersion = JavaLanguageVersion.of(8) 36 | } 37 | } 38 | 39 | jar { 40 | manifest { 41 | attributes([ 42 | "Specification-Title": "friendapi", 43 | "Specification-Vendor": "utilitymods", 44 | "Specification-Version": "1", 45 | "Implementation-Title": project.name, 46 | "Implementation-Version": "${version}", 47 | "Implementation-Vendor" :"utilitymods", 48 | "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") 49 | ]) 50 | } 51 | } 52 | 53 | jar.finalizedBy('reobfJar') 54 | -------------------------------------------------------------------------------- /FriendAPI-12/src/main/java/org/utilitymods/friendapi/ForgeProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.EntityPlayer; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.utilitymods.friendapi.profiles.Affinity; 7 | import org.utilitymods.friendapi.profiles.Profile; 8 | import org.utilitymods.friendapi.profiles.ProfileFactory; 9 | 10 | /** 11 | * A factory for creating {@link Profile}s. 12 | * Allows you to use the raw player object to create a profile. 13 | */ 14 | public final class ForgeProfileFactory extends ProfileFactory { 15 | 16 | /** 17 | * Singleton instance of the factory. 18 | */ 19 | public static final ForgeProfileFactory INSTANCE = new ForgeProfileFactory(); 20 | 21 | public Profile createProfile(@NotNull GameProfile profile, @NotNull Affinity affinity) { 22 | return createProfile(profile.getName(), profile.getId(), affinity); 23 | } 24 | 25 | public Profile createProfile(@NotNull GameProfile profile) { 26 | return createProfile(profile, Affinity.NEUTRAL); 27 | } 28 | 29 | public Profile createFriend(@NotNull GameProfile profile) { 30 | return createProfile(profile, Affinity.FRIEND); 31 | } 32 | 33 | public Profile createEnemy(@NotNull GameProfile profile) { 34 | return createProfile(profile, Affinity.ENEMY); 35 | } 36 | 37 | public Profile createProfile(@NotNull EntityPlayer player, @NotNull Affinity affinity) { 38 | return createProfile(player.getGameProfile(), affinity); 39 | } 40 | 41 | public Profile createProfile(@NotNull EntityPlayer player) { 42 | return createProfile(player.getGameProfile()); 43 | } 44 | 45 | public Profile createFriend(@NotNull EntityPlayer player) { 46 | return createFriend(player.getGameProfile()); 47 | } 48 | 49 | public Profile createEnemy(@NotNull EntityPlayer player) { 50 | return createEnemy(player.getGameProfile()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /FriendAPI-12/src/main/java/org/utilitymods/friendapi/FriendMod.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import net.minecraftforge.fml.common.Mod; 4 | 5 | @Mod(modid = "friendapi", name = "FriendAPI", version = "1.0.0") 6 | public class FriendMod { 7 | 8 | @Mod.EventHandler 9 | public void preInit() { 10 | FriendManager.getInstance().setFactory(ForgeProfileFactory.INSTANCE); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FriendAPI-12/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "friendapi", 4 | "name": "FriendAPI", 5 | "description": "This library add support for the FriendAPI which allows for the same friends across client, versions, and instances", 6 | "version": "1.1.0", 7 | "authorList": ["Utility Mods"] 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /FriendAPI-16/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.10-SNAPSHOT' 3 | } 4 | 5 | archivesBaseName = "FriendAPI-16" 6 | 7 | 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | 11 | dependencies { 12 | api project(':FriendAPI') 13 | library(project(':FriendAPI')) { 14 | transitive false 15 | } 16 | 17 | minecraft "com.mojang:minecraft:${project.minecraft_version_16}" 18 | mappings "net.fabricmc:yarn:${project.yarn_mappings_16}:v2" 19 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version_16}" 20 | 21 | modImplementation include(fabricApi.module("fabric-api-base", project.fabric_version_16)) 22 | modImplementation include(fabricApi.module("fabric-command-api-v1", project.fabric_version_16)) 23 | } 24 | 25 | processResources { 26 | inputs.property "version", project.version 27 | 28 | filesMatching("fabric.mod.json") { 29 | expand "version": project.version 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/FabricProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.utilitymods.friendapi.profiles.Affinity; 7 | import org.utilitymods.friendapi.profiles.Profile; 8 | import org.utilitymods.friendapi.profiles.ProfileFactory; 9 | 10 | /** 11 | * A factory for creating {@link Profile}s. 12 | * Allows you to use the raw player object to create a profile. 13 | */ 14 | public final class FabricProfileFactory extends ProfileFactory { 15 | 16 | /** 17 | * Singleton instance of the factory. 18 | */ 19 | public static final FabricProfileFactory INSTANCE = new FabricProfileFactory(); 20 | 21 | public Profile createProfile(@NotNull GameProfile profile, @NotNull Affinity affinity) { 22 | return createProfile(profile.getName(), profile.getId(), affinity); 23 | } 24 | 25 | public Profile createProfile(@NotNull GameProfile profile) { 26 | return createProfile(profile, Affinity.NEUTRAL); 27 | } 28 | 29 | public Profile createFriend(@NotNull GameProfile profile) { 30 | return createProfile(profile, Affinity.FRIEND); 31 | } 32 | 33 | public Profile createEnemy(@NotNull GameProfile profile) { 34 | return createProfile(profile, Affinity.ENEMY); 35 | } 36 | 37 | public Profile createProfile(@NotNull PlayerEntity player, @NotNull Affinity affinity) { 38 | return createProfile(player.getGameProfile(), affinity); 39 | } 40 | 41 | public Profile createProfile(@NotNull PlayerEntity player) { 42 | return createProfile(player.getGameProfile()); 43 | } 44 | 45 | public Profile createFriend(@NotNull PlayerEntity player) { 46 | return createFriend(player.getGameProfile()); 47 | } 48 | 49 | public Profile createEnemy(@NotNull PlayerEntity player) { 50 | return createEnemy(player.getGameProfile()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/FriendMod.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.minecraft.command.argument.ArgumentTypes; 6 | import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; 7 | import org.utilitymods.friendapi.FriendManager; 8 | import org.utilitymods.friendapi.command.Commands; 9 | import org.utilitymods.friendapi.command.FriendArgument; 10 | 11 | public class FriendMod implements ModInitializer { 12 | 13 | @Override 14 | public void onInitialize() { 15 | FriendManager.getInstance().setFactory(FabricProfileFactory.INSTANCE); 16 | 17 | Commands commands = new Commands(); 18 | 19 | ArgumentTypes.register("friendapi:friend", FriendArgument.class, new ConstantArgumentSerializer<>(FriendArgument::friendArgument)); 20 | 21 | ClientCommandManager.DISPATCHER.register(ClientCommandManager.literal("friendapi") 22 | .then(commands.add) 23 | .then(commands.remove) 24 | .then(commands.list)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/command/AffinityArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.suggestion.Suggestions; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | import net.minecraft.command.CommandSource; 9 | import org.utilitymods.friendapi.profiles.Affinity; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.concurrent.CompletableFuture; 14 | 15 | public class AffinityArgument implements ArgumentType { 16 | private static final Collection EXAMPLES = Arrays.asList("friend", "enemy", "neutral"); 17 | 18 | private AffinityArgument() { 19 | } 20 | 21 | public static AffinityArgument affinity() { 22 | return new AffinityArgument(); 23 | } 24 | 25 | public static Affinity getAffinity(CommandContext context, String name) { 26 | return context.getArgument(name, Affinity.class); 27 | } 28 | 29 | public Affinity parse(StringReader stringReader) { 30 | String string = stringReader.readUnquotedString(); 31 | Affinity affinity = Affinity.byName(string); 32 | if (affinity != null) { 33 | return affinity; 34 | } else { 35 | return Affinity.NEUTRAL; 36 | } 37 | } 38 | 39 | @Override 40 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 41 | return CommandSource.suggestMatching(getExamples(), builder); 42 | } 43 | 44 | @Override 45 | public Collection getExamples() { 46 | return EXAMPLES; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.tree.LiteralCommandNode; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.text.*; 9 | import net.minecraft.util.Formatting; 10 | import org.utilitymods.friendapi.FabricProfileFactory; 11 | import org.utilitymods.friendapi.profiles.Affinity; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Locale; 16 | 17 | import static com.mojang.brigadier.Command.SINGLE_SUCCESS; 18 | 19 | public class Commands { 20 | 21 | private static final String COMMAND_BASE = "/friendapi "; 22 | 23 | public final LiteralCommandNode add = ClientCommandManager.literal("add") 24 | .then(ClientCommandManager.argument("name", new PlayerArgument(MinecraftClient.getInstance())) 25 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), Affinity.FRIEND)) 26 | .then(ClientCommandManager.argument("affinity", AffinityArgument.affinity()) 27 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), AffinityArgument.getAffinity(context, "affinity")))) 28 | ).build(); 29 | 30 | public final LiteralCommandNode remove = ClientCommandManager.literal("remove") 31 | .then(ClientCommandManager.argument("name", new FriendArgument()) 32 | .executes(context -> { 33 | Profile player = context.getArgument("name", Profile.class); 34 | FriendManager.getInstance().removeFriend(player.getUUID()); 35 | context.getSource().sendFeedback(new TranslatableText("commands.friendapi.removed", player.getName()).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, COMMAND_BASE + "add " + player.getName())).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.add"))))); 36 | return SINGLE_SUCCESS; 37 | })).build(); 38 | 39 | public final LiteralCommandNode list = ClientCommandManager.literal("list") 40 | .then(ClientCommandManager.literal("friends") 41 | .executes(context -> executeList(context.getSource(), true, false))) 42 | .then(ClientCommandManager.literal("enemies") 43 | .executes(context -> executeList(context.getSource(), false, true))) 44 | .executes(context -> executeList(context.getSource(), true, true)) 45 | .build(); 46 | 47 | private int executeAdd(FabricClientCommandSource source, PlayerEntity player, Affinity affinity) { 48 | if (FriendManager.getInstance().getAffinity(player.getGameProfile().getId()).equals(affinity)) { 49 | 50 | source.sendFeedback(new TranslatableText("commands.friendapi.already", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 51 | } else { 52 | FriendManager.getInstance().addFriend(FabricProfileFactory.INSTANCE.createProfile(player, affinity)); 53 | source.sendFeedback(new TranslatableText("commands.friendapi.now", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 54 | } 55 | return SINGLE_SUCCESS; 56 | } 57 | 58 | private int executeList(FabricClientCommandSource source, boolean friends, boolean enemies) { 59 | for (Profile profile : FriendManager.getInstance().getOnlyAllProfiles()) { 60 | if (!(profile.getAffinity().equals(Affinity.FRIEND) && friends) && !(profile.getAffinity().equals(Affinity.ENEMY) && enemies)) 61 | continue; 62 | source.sendFeedback(getTextOfProfile(profile)); 63 | } 64 | return SINGLE_SUCCESS; 65 | } 66 | 67 | private Text getTextOfProfile(Profile profile) { 68 | return new LiteralText(getGrayedLabel("Name") + profile.getName() + " " + getGrayedLabel("Affinity") + getColoredAffinity(profile.getAffinity())).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, profile.getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove")))); 69 | } 70 | 71 | private String getGrayedLabel(String name) { 72 | return Formatting.GRAY + name + ": " + Formatting.RESET; 73 | } 74 | 75 | private String getColoredAffinity(Affinity affinity) { 76 | Formatting formatting; 77 | switch (affinity) { 78 | case NEUTRAL: 79 | formatting = Formatting.YELLOW; 80 | break; 81 | case ENEMY: 82 | formatting = Formatting.RED; 83 | break; 84 | case FRIEND: 85 | formatting = Formatting.GREEN; 86 | break; 87 | default: 88 | formatting = Formatting.BOLD; 89 | break; 90 | } 91 | return formatting + affinity.name().toLowerCase(Locale.ROOT) + Formatting.RESET; 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/command/FriendArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 8 | import com.mojang.brigadier.suggestion.Suggestions; 9 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.text.LiteralText; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | import java.util.stream.Collectors; 18 | 19 | public class FriendArgument implements ArgumentType { 20 | 21 | public static FriendArgument friendArgument() { 22 | return new FriendArgument(); 23 | } 24 | 25 | private final DynamicCommandExceptionType invalidArg = new DynamicCommandExceptionType(o -> new LiteralText(o + " is not a friend")); 26 | 27 | @Override 28 | public Profile parse(StringReader reader) throws CommandSyntaxException { 29 | String argument = reader.readString(); 30 | Profile profile = null; 31 | for (Profile s : FriendManager.getInstance().getFriendMapCopy().values()) { 32 | if (s.getName().equalsIgnoreCase(argument)) { 33 | profile = s; 34 | break; 35 | } 36 | } 37 | if (profile == null) throw invalidArg.create(argument); 38 | return profile; 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return FriendManager.getInstance().getFriendMapCopy().values().stream().map(Profile::getName).collect(Collectors.toList()); 44 | } 45 | 46 | @Override 47 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 48 | return CommandSource.suggestMatching(getExamples(), builder); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /FriendAPI-16/src/main/java/org/utilitymods/friendapi/command/PlayerArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.suggestion.Suggestions; 8 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.command.argument.EntityArgumentType; 12 | import net.minecraft.entity.player.PlayerEntity; 13 | 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class PlayerArgument implements ArgumentType { 19 | private static final Collection EXAMPLES = (Arrays.asList("Player", "ChiquitaV2", "Joe", "Dream")); 20 | private final MinecraftClient mc; 21 | 22 | public PlayerArgument(MinecraftClient mc) { 23 | this.mc = mc; 24 | } 25 | 26 | @Override 27 | public PlayerEntity parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | PlayerEntity playerEntity = null; 30 | assert mc.world != null; 31 | for (PlayerEntity p : mc.world.getPlayers()) { 32 | if (p.getDisplayName().asString().equalsIgnoreCase(argument)) { 33 | playerEntity = p; 34 | break; 35 | } 36 | } 37 | if (playerEntity == null) throw EntityArgumentType.NOT_ALLOWED_EXCEPTION.createWithContext(reader); 38 | return playerEntity; 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | 46 | @Override 47 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 48 | if (context.getSource() instanceof CommandSource) { 49 | StringReader stringReader = new StringReader(builder.getInput()); 50 | stringReader.setCursor(builder.getStart()); 51 | CommandSource commandSource = (CommandSource) context.getSource(); 52 | 53 | return CommandSource.suggestMatching(commandSource.getPlayerNames(), builder); 54 | } else { 55 | return Suggestions.empty(); 56 | } 57 | 58 | } 59 | 60 | public static PlayerEntity getPlayer(CommandContext context, String name) { 61 | return context.getArgument(name, PlayerEntity.class); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FriendAPI-16/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "friendapi", 4 | "version": "${version}", 5 | "name": "FriendAPI", 6 | "description": "This library add support for the FriendAPI which allows for the same friends across client, versions, and instances", 7 | "authors": [ 8 | "Utility Mods" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/UtilityMods/FriendsAPI" 12 | }, 13 | "entrypoints": { 14 | "main": [ 15 | "org.utilitymods.friendapi.FriendMod" 16 | ] 17 | }, 18 | "license": "Apache-2.0", 19 | "icon": "assets/friendapi/icon.png", 20 | "depends": { 21 | "minecraft": "1.16.5" 22 | }, 23 | "custom": { 24 | "modmenu": { 25 | "badges": [ 26 | "library" 27 | ], 28 | "links": { 29 | "modmenu.discord": "https://discord.gg/h8EQyuYTK7" 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /FriendAPI-17/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.10-SNAPSHOT' 3 | } 4 | 5 | archivesBaseName = "FriendAPI-17" 6 | 7 | sourceCompatibility = JavaVersion.VERSION_16 8 | targetCompatibility = JavaVersion.VERSION_16 9 | 10 | dependencies { 11 | api project(':FriendAPI') 12 | library(project(':FriendAPI')) { 13 | transitive false 14 | } 15 | 16 | minecraft "com.mojang:minecraft:${project.minecraft_version_17}" 17 | mappings "net.fabricmc:yarn:${project.yarn_mappings_17}:v2" 18 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version_17}" 19 | 20 | modImplementation include(fabricApi.module("fabric-api-base", project.fabric_version_17)) 21 | modImplementation include(fabricApi.module("fabric-command-api-v1", project.fabric_version_17)) 22 | } 23 | 24 | processResources { 25 | inputs.property "version", project.version 26 | 27 | filesMatching("fabric.mod.json") { 28 | expand "version": project.version 29 | } 30 | 31 | 32 | } 33 | 34 | tasks.withType(JavaCompile).configureEach { 35 | it.options.release = 16 36 | } 37 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/FabricProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.utilitymods.friendapi.profiles.Affinity; 7 | import org.utilitymods.friendapi.profiles.Profile; 8 | import org.utilitymods.friendapi.profiles.ProfileFactory; 9 | 10 | /** 11 | * A factory for creating {@link Profile}s. 12 | * Allows you to use the raw player object to create a profile. 13 | */ 14 | public final class FabricProfileFactory extends ProfileFactory { 15 | 16 | /** 17 | * Singleton instance of the factory. 18 | */ 19 | public static final FabricProfileFactory INSTANCE = new FabricProfileFactory(); 20 | 21 | public Profile createProfile(@NotNull GameProfile profile, @NotNull Affinity affinity) { 22 | return createProfile(profile.getName(), profile.getId(), affinity); 23 | } 24 | 25 | public Profile createProfile(@NotNull GameProfile profile) { 26 | return createProfile(profile, Affinity.NEUTRAL); 27 | } 28 | 29 | public Profile createFriend(@NotNull GameProfile profile) { 30 | return createProfile(profile, Affinity.FRIEND); 31 | } 32 | 33 | public Profile createEnemy(@NotNull GameProfile profile) { 34 | return createProfile(profile, Affinity.ENEMY); 35 | } 36 | 37 | public Profile createProfile(@NotNull PlayerEntity player, @NotNull Affinity affinity) { 38 | return createProfile(player.getGameProfile(), affinity); 39 | } 40 | 41 | public Profile createProfile(@NotNull PlayerEntity player) { 42 | return createProfile(player.getGameProfile()); 43 | } 44 | 45 | public Profile createFriend(@NotNull PlayerEntity player) { 46 | return createFriend(player.getGameProfile()); 47 | } 48 | 49 | public Profile createEnemy(@NotNull PlayerEntity player) { 50 | return createEnemy(player.getGameProfile()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/FriendMod.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.minecraft.command.argument.ArgumentTypes; 6 | import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; 7 | import org.utilitymods.friendapi.FriendManager; 8 | import org.utilitymods.friendapi.command.Commands; 9 | import org.utilitymods.friendapi.command.FriendArgument; 10 | 11 | public class FriendMod implements ModInitializer { 12 | 13 | @Override 14 | public void onInitialize() { 15 | FriendManager.getInstance(); 16 | 17 | Commands commands = new Commands(); 18 | 19 | ArgumentTypes.register("friendapi:friend", FriendArgument.class, new ConstantArgumentSerializer<>(FriendArgument::friendArgument)); 20 | 21 | ClientCommandManager.DISPATCHER.register(ClientCommandManager.literal("friendapi") 22 | .then(commands.add) 23 | .then(commands.remove) 24 | .then(commands.list)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/command/AffinityArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.suggestion.Suggestions; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | import net.minecraft.command.CommandSource; 9 | import org.utilitymods.friendapi.profiles.Affinity; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.Objects; 14 | import java.util.concurrent.CompletableFuture; 15 | 16 | public class AffinityArgument implements ArgumentType { 17 | private static final Collection EXAMPLES = Arrays.asList("friend", "enemy", "neutral"); 18 | 19 | private AffinityArgument() { 20 | } 21 | 22 | public static AffinityArgument affinity() { 23 | return new AffinityArgument(); 24 | } 25 | 26 | public static Affinity getAffinity(CommandContext context, String name) { 27 | return context.getArgument(name, Affinity.class); 28 | } 29 | 30 | public Affinity parse(StringReader stringReader) { 31 | String string = stringReader.readUnquotedString(); 32 | Affinity affinity = Affinity.byName(string); 33 | return Objects.requireNonNullElse(affinity, Affinity.NEUTRAL); 34 | } 35 | 36 | @Override 37 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 38 | return CommandSource.suggestMatching(getExamples(), builder); 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.tree.LiteralCommandNode; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.text.*; 9 | import net.minecraft.util.Formatting; 10 | import org.utilitymods.friendapi.FabricProfileFactory; 11 | import org.utilitymods.friendapi.profiles.Affinity; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Locale; 16 | 17 | import static com.mojang.brigadier.Command.SINGLE_SUCCESS; 18 | 19 | public class Commands { 20 | private static final String COMMAND_BASE = "/friendapi "; 21 | 22 | public final LiteralCommandNode add = ClientCommandManager.literal("add") 23 | .then(ClientCommandManager.argument("name", new PlayerArgument(MinecraftClient.getInstance())) 24 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), Affinity.FRIEND)) 25 | .then(ClientCommandManager.argument("affinity", AffinityArgument.affinity()) 26 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), AffinityArgument.getAffinity(context, "affinity")))) 27 | ).build(); 28 | 29 | public final LiteralCommandNode remove = ClientCommandManager.literal("remove") 30 | .then(ClientCommandManager.argument("name", new FriendArgument()) 31 | .executes(context -> { 32 | Profile player = context.getArgument("name", Profile.class); 33 | FriendManager.getInstance().removeFriend(player.getUUID()); 34 | context.getSource().sendFeedback(new TranslatableText("commands.friendapi.removed", player.getName()).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, COMMAND_BASE + "add " + player.getName())).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.add"))))); 35 | return SINGLE_SUCCESS; 36 | })).build(); 37 | 38 | public final LiteralCommandNode list = ClientCommandManager.literal("list") 39 | .then(ClientCommandManager.literal("friends") 40 | .executes(context -> executeList(context.getSource(), true, false))) 41 | .then(ClientCommandManager.literal("enemies") 42 | .executes(context -> executeList(context.getSource(), false, true))) 43 | .executes(context -> executeList(context.getSource(), true, true)) 44 | .build(); 45 | 46 | private int executeAdd(FabricClientCommandSource source, PlayerEntity player, Affinity affinity) { 47 | if (FriendManager.getInstance().getAffinity(player.getGameProfile().getId()).equals(affinity)) { 48 | 49 | source.sendFeedback(new TranslatableText("commands.friendapi.already", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 50 | } else { 51 | FriendManager.getInstance().addFriend(FabricProfileFactory.INSTANCE.createProfile(player, affinity)); 52 | 53 | source.sendFeedback(new TranslatableText("commands.friendapi.now", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 54 | } 55 | return SINGLE_SUCCESS; 56 | } 57 | 58 | private int executeList(FabricClientCommandSource source, boolean friends, boolean enemies) { 59 | for (Profile profile : FriendManager.getInstance().getOnlyAllProfiles()) { 60 | if (!(profile.getAffinity().equals(Affinity.FRIEND) && friends) && !(profile.getAffinity().equals(Affinity.ENEMY) && enemies)) 61 | continue; 62 | source.sendFeedback(getTextOfProfile(profile)); 63 | } 64 | return SINGLE_SUCCESS; 65 | } 66 | 67 | private Text getTextOfProfile(Profile profile) { 68 | return new LiteralText(getGrayedLabel("Name") + profile.getName() + " " + getGrayedLabel("Affinity") + getColoredAffinity(profile.getAffinity())).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, profile.getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove")))); 69 | } 70 | 71 | private String getGrayedLabel(String name) { 72 | return Formatting.GRAY + name + ": " + Formatting.RESET; 73 | } 74 | 75 | private String getColoredAffinity(Affinity affinity) { 76 | Formatting formatting = switch (affinity) { 77 | case NEUTRAL -> Formatting.YELLOW; 78 | case ENEMY -> Formatting.RED; 79 | case FRIEND -> Formatting.GREEN; 80 | }; 81 | return formatting + affinity.name().toLowerCase(Locale.ROOT) + Formatting.RESET; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/command/FriendArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 8 | import com.mojang.brigadier.suggestion.Suggestions; 9 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.text.LiteralText; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class FriendArgument implements ArgumentType { 19 | 20 | public static FriendArgument friendArgument() { 21 | return new FriendArgument(); 22 | } 23 | 24 | private final DynamicCommandExceptionType invalidArg = new DynamicCommandExceptionType(o -> new LiteralText(o + " is not a friend")); 25 | 26 | @Override 27 | public Profile parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | Profile profile = null; 30 | for (Profile s : FriendManager.getInstance().getFriendMapCopy().values()) { 31 | if (s.getName().equalsIgnoreCase(argument)) { 32 | profile = s; 33 | break; 34 | } 35 | } 36 | if (profile == null) throw invalidArg.create(argument); 37 | return profile; 38 | } 39 | 40 | @Override 41 | public Collection getExamples() { 42 | return FriendManager.getInstance().getFriendMapCopy().values().stream().map(Profile::getName).toList(); 43 | } 44 | 45 | @Override 46 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 47 | return CommandSource.suggestMatching(getExamples(), builder); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /FriendAPI-17/src/main/java/org/utilitymods/friendapi/command/PlayerArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.suggestion.Suggestions; 8 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.command.argument.EntityArgumentType; 12 | import net.minecraft.entity.player.PlayerEntity; 13 | 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class PlayerArgument implements ArgumentType { 19 | private static final Collection EXAMPLES = (Arrays.asList("Player", "ChiquitaV2", "Joe", "Dream")); 20 | private final MinecraftClient mc; 21 | 22 | public PlayerArgument(MinecraftClient mc) { 23 | this.mc = mc; 24 | } 25 | 26 | @Override 27 | public PlayerEntity parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | PlayerEntity playerEntity = null; 30 | assert mc.world != null; 31 | for (PlayerEntity p : mc.world.getPlayers()) { 32 | if (p.getDisplayName().asString().equalsIgnoreCase(argument)) { 33 | playerEntity = p; 34 | break; 35 | } 36 | } 37 | if (playerEntity == null) throw EntityArgumentType.NOT_ALLOWED_EXCEPTION.createWithContext(reader); 38 | return playerEntity; 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | 46 | @Override 47 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 48 | if (context.getSource() instanceof CommandSource commandSource) { 49 | StringReader stringReader = new StringReader(builder.getInput()); 50 | stringReader.setCursor(builder.getStart()); 51 | 52 | return CommandSource.suggestMatching(commandSource.getPlayerNames(), builder); 53 | } else { 54 | return Suggestions.empty(); 55 | } 56 | 57 | } 58 | 59 | public static PlayerEntity getPlayer(CommandContext context, String name) { 60 | return context.getArgument(name, PlayerEntity.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FriendAPI-17/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "friendapi", 4 | "version": "${version}", 5 | "name": "FriendAPI", 6 | "description": "This library add support for the FriendAPI which allows for the same friends across client, versions, and instances", 7 | "authors": [ 8 | "Utility Mods" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/UtilityMods/FriendsAPI" 12 | }, 13 | "entrypoints": { 14 | "main": [ 15 | "org.utilitymods.friendapi.FriendMod" 16 | ] 17 | }, 18 | "license": "Apache-2.0", 19 | "icon": "assets/friendapi/icon.png", 20 | "depends": { 21 | "fabricloader": ">=0.11.3", 22 | "minecraft": "1.17.x" 23 | }, 24 | "custom": { 25 | "modmenu": { 26 | "badges": [ 27 | "library" 28 | ], 29 | "links": { 30 | "modmenu.discord": "https://discord.gg/h8EQyuYTK7" 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FriendAPI-18/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.10-SNAPSHOT' 3 | } 4 | 5 | archivesBaseName = "FriendAPI-18" 6 | 7 | sourceCompatibility = JavaVersion.VERSION_17 8 | targetCompatibility = JavaVersion.VERSION_17 9 | 10 | dependencies { 11 | api project(':FriendAPI') 12 | library(project(':FriendAPI')) { 13 | transitive false 14 | } 15 | 16 | minecraft "com.mojang:minecraft:${project.minecraft_version_18}" 17 | mappings "net.fabricmc:yarn:${project.yarn_mappings_18}:v2" 18 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version_18}" 19 | 20 | modImplementation include(fabricApi.module("fabric-api-base", project.fabric_version_18)) 21 | modImplementation include(fabricApi.module("fabric-command-api-v1", project.fabric_version_18)) 22 | } 23 | 24 | processResources { 25 | inputs.property "version", project.version 26 | 27 | filesMatching("fabric.mod.json") { 28 | expand "version": project.version 29 | } 30 | 31 | 32 | } 33 | 34 | tasks.withType(JavaCompile).configureEach { 35 | it.options.release = 17 36 | } -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/FabricProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.utilitymods.friendapi.profiles.Affinity; 7 | import org.utilitymods.friendapi.profiles.Profile; 8 | import org.utilitymods.friendapi.profiles.ProfileFactory; 9 | 10 | /** 11 | * A factory for creating {@link Profile}s. 12 | * Allows you to use the raw player object to create a profile. 13 | */ 14 | public final class FabricProfileFactory extends ProfileFactory { 15 | 16 | /** 17 | * Singleton instance of the factory. 18 | */ 19 | public static final FabricProfileFactory INSTANCE = new FabricProfileFactory(); 20 | 21 | public Profile createProfile(@NotNull GameProfile profile, @NotNull Affinity affinity) { 22 | return createProfile(profile.getName(), profile.getId(), affinity); 23 | } 24 | 25 | public Profile createProfile(@NotNull GameProfile profile) { 26 | return createProfile(profile, Affinity.NEUTRAL); 27 | } 28 | 29 | public Profile createFriend(@NotNull GameProfile profile) { 30 | return createProfile(profile, Affinity.FRIEND); 31 | } 32 | 33 | public Profile createEnemy(@NotNull GameProfile profile) { 34 | return createProfile(profile, Affinity.ENEMY); 35 | } 36 | 37 | public Profile createProfile(@NotNull PlayerEntity player, @NotNull Affinity affinity) { 38 | return createProfile(player.getGameProfile(), affinity); 39 | } 40 | 41 | public Profile createProfile(@NotNull PlayerEntity player) { 42 | return createProfile(player.getGameProfile()); 43 | } 44 | 45 | public Profile createFriend(@NotNull PlayerEntity player) { 46 | return createFriend(player.getGameProfile()); 47 | } 48 | 49 | public Profile createEnemy(@NotNull PlayerEntity player) { 50 | return createEnemy(player.getGameProfile()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/FriendMod.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.minecraft.command.argument.ArgumentTypes; 6 | import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; 7 | import org.utilitymods.friendapi.FriendManager; 8 | import org.utilitymods.friendapi.command.Commands; 9 | import org.utilitymods.friendapi.command.FriendArgument; 10 | 11 | public class FriendMod implements ModInitializer { 12 | 13 | @Override 14 | public void onInitialize() { 15 | FriendManager.getInstance(); 16 | 17 | Commands commands = new Commands(); 18 | 19 | ArgumentTypes.register("friendapi:friend", FriendArgument.class, new ConstantArgumentSerializer<>(FriendArgument::friendArgument)); 20 | 21 | ClientCommandManager.DISPATCHER.register(ClientCommandManager.literal("friendapi") 22 | .then(commands.add) 23 | .then(commands.remove) 24 | .then(commands.list)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/command/AffinityArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.suggestion.Suggestions; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | import net.minecraft.command.CommandSource; 9 | import org.utilitymods.friendapi.profiles.Affinity; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.Objects; 14 | import java.util.concurrent.CompletableFuture; 15 | 16 | public class AffinityArgument implements ArgumentType { 17 | private static final Collection EXAMPLES = Arrays.asList("friend", "enemy", "neutral"); 18 | 19 | private AffinityArgument() { 20 | } 21 | 22 | public static AffinityArgument affinity() { 23 | return new AffinityArgument(); 24 | } 25 | 26 | public static Affinity getAffinity(CommandContext context, String name) { 27 | return context.getArgument(name, Affinity.class); 28 | } 29 | 30 | public Affinity parse(StringReader stringReader) { 31 | String string = stringReader.readUnquotedString(); 32 | Affinity affinity = Affinity.byName(string); 33 | return Objects.requireNonNullElse(affinity, Affinity.NEUTRAL); 34 | } 35 | 36 | @Override 37 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 38 | return CommandSource.suggestMatching(getExamples(), builder); 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.tree.LiteralCommandNode; 4 | import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.text.*; 9 | import net.minecraft.util.Formatting; 10 | import org.utilitymods.friendapi.FabricProfileFactory; 11 | import org.utilitymods.friendapi.profiles.Affinity; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Locale; 16 | 17 | import static com.mojang.brigadier.Command.SINGLE_SUCCESS; 18 | 19 | public class Commands { 20 | private static final String COMMAND_BASE = "/friendapi "; 21 | 22 | public final LiteralCommandNode add = ClientCommandManager.literal("add") 23 | .then(ClientCommandManager.argument("name", new PlayerArgument(MinecraftClient.getInstance())) 24 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), Affinity.FRIEND)) 25 | .then(ClientCommandManager.argument("affinity", AffinityArgument.affinity()) 26 | .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), AffinityArgument.getAffinity(context, "affinity")))) 27 | ).build(); 28 | 29 | public final LiteralCommandNode remove = ClientCommandManager.literal("remove") 30 | .then(ClientCommandManager.argument("name", new FriendArgument()) 31 | .executes(context -> { 32 | Profile player = context.getArgument("name", Profile.class); 33 | FriendManager.getInstance().removeFriend(player.getUUID()); 34 | context.getSource().sendFeedback(new TranslatableText("commands.friendapi.removed", player.getName()).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, COMMAND_BASE + "add " + player.getName())).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.add"))))); 35 | return SINGLE_SUCCESS; 36 | })).build(); 37 | 38 | public final LiteralCommandNode list = ClientCommandManager.literal("list") 39 | .then(ClientCommandManager.literal("friends") 40 | .executes(context -> executeList(context.getSource(), true, false))) 41 | .then(ClientCommandManager.literal("enemies") 42 | .executes(context -> executeList(context.getSource(), false, true))) 43 | .executes(context -> executeList(context.getSource(), true, true)) 44 | .build(); 45 | 46 | private int executeAdd(FabricClientCommandSource source, PlayerEntity player, Affinity affinity) { 47 | if (FriendManager.getInstance().getAffinity(player.getGameProfile().getId()).equals(affinity)) { 48 | 49 | source.sendFeedback(new TranslatableText("commands.friendapi.already", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 50 | } else { 51 | FriendManager.getInstance().addFriend(FabricProfileFactory.INSTANCE.createProfile(player, affinity)); 52 | 53 | source.sendFeedback(new TranslatableText("commands.friendapi.now", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove"))))); 54 | } 55 | return SINGLE_SUCCESS; 56 | } 57 | 58 | private int executeList(FabricClientCommandSource source, boolean friends, boolean enemies) { 59 | for (Profile profile : FriendManager.getInstance().getOnlyAllProfiles()) { 60 | if (!(profile.getAffinity().equals(Affinity.FRIEND) && friends) && !(profile.getAffinity().equals(Affinity.ENEMY) && enemies)) 61 | continue; 62 | source.sendFeedback(getTextOfProfile(profile)); 63 | } 64 | return SINGLE_SUCCESS; 65 | } 66 | 67 | private Text getTextOfProfile(Profile profile) { 68 | return new LiteralText(getGrayedLabel("Name") + profile.getName() + " " + getGrayedLabel("Affinity") + getColoredAffinity(profile.getAffinity())).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, profile.getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands.friendapi.remove")))); 69 | } 70 | 71 | private String getGrayedLabel(String name) { 72 | return Formatting.GRAY + name + ": " + Formatting.RESET; 73 | } 74 | 75 | private String getColoredAffinity(Affinity affinity) { 76 | Formatting formatting = switch (affinity) { 77 | case NEUTRAL -> Formatting.YELLOW; 78 | case ENEMY -> Formatting.RED; 79 | case FRIEND -> Formatting.GREEN; 80 | }; 81 | return formatting + affinity.name().toLowerCase(Locale.ROOT) + Formatting.RESET; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/command/FriendArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 8 | import com.mojang.brigadier.suggestion.Suggestions; 9 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.text.LiteralText; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class FriendArgument implements ArgumentType { 19 | 20 | public static FriendArgument friendArgument() { 21 | return new FriendArgument(); 22 | } 23 | 24 | private final DynamicCommandExceptionType invalidArg = new DynamicCommandExceptionType(o -> new LiteralText(o + " is not a friend")); 25 | 26 | @Override 27 | public Profile parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | Profile profile = null; 30 | for (Profile s : FriendManager.getInstance().getFriendMapCopy().values()) { 31 | if (s.getName().equalsIgnoreCase(argument)) { 32 | profile = s; 33 | break; 34 | } 35 | } 36 | if (profile == null) throw invalidArg.create(argument); 37 | return profile; 38 | } 39 | 40 | @Override 41 | public Collection getExamples() { 42 | return FriendManager.getInstance().getFriendMapCopy().values().stream().map(Profile::getName).toList(); 43 | } 44 | 45 | @Override 46 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 47 | return CommandSource.suggestMatching(getExamples(), builder); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /FriendAPI-18/src/main/java/org/utilitymods/friendapi/command/PlayerArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.suggestion.Suggestions; 8 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.command.argument.EntityArgumentType; 12 | import net.minecraft.entity.player.PlayerEntity; 13 | 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class PlayerArgument implements ArgumentType { 19 | private static final Collection EXAMPLES = (Arrays.asList("Player", "ChiquitaV2", "Joe", "Dream")); 20 | private final MinecraftClient mc; 21 | 22 | public PlayerArgument(MinecraftClient mc) { 23 | this.mc = mc; 24 | } 25 | 26 | @Override 27 | public PlayerEntity parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | PlayerEntity playerEntity = null; 30 | assert mc.world != null; 31 | for (PlayerEntity p : mc.world.getPlayers()) { 32 | if (p.getDisplayName().asString().equalsIgnoreCase(argument)) { 33 | playerEntity = p; 34 | break; 35 | } 36 | } 37 | if (playerEntity == null) throw EntityArgumentType.NOT_ALLOWED_EXCEPTION.createWithContext(reader); 38 | return playerEntity; 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | 46 | @Override 47 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 48 | if (context.getSource() instanceof CommandSource commandSource) { 49 | StringReader stringReader = new StringReader(builder.getInput()); 50 | stringReader.setCursor(builder.getStart()); 51 | 52 | return CommandSource.suggestMatching(commandSource.getPlayerNames(), builder); 53 | } else { 54 | return Suggestions.empty(); 55 | } 56 | 57 | } 58 | 59 | public static PlayerEntity getPlayer(CommandContext context, String name) { 60 | return context.getArgument(name, PlayerEntity.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FriendAPI-18/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "friendapi", 4 | "version": "${version}", 5 | "name": "FriendAPI", 6 | "description": "This library add support for the FriendAPI which allows for the same friends across client, versions, and instances", 7 | "authors": [ 8 | "Utility Mods" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/UtilityMods/FriendsAPI" 12 | }, 13 | "entrypoints": { 14 | "main": [ 15 | "org.utilitymods.friendapi.FriendMod" 16 | ] 17 | }, 18 | "license": "Apache-2.0", 19 | "icon": "assets/friendapi/icon.png", 20 | "depends": { 21 | "fabricloader": ">=0.12.8", 22 | "minecraft": "1.18.x" 23 | }, 24 | "custom": { 25 | "modmenu": { 26 | "badges": [ 27 | "library" 28 | ], 29 | "links": { 30 | "modmenu.discord": "https://discord.gg/h8EQyuYTK7" 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FriendAPI-19/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.12-SNAPSHOT' 3 | } 4 | 5 | archivesBaseName = "FriendAPI-19" 6 | 7 | sourceCompatibility = JavaVersion.VERSION_17 8 | targetCompatibility = JavaVersion.VERSION_17 9 | 10 | dependencies { 11 | api project(':FriendAPI') 12 | library(project(':FriendAPI')) { 13 | transitive false 14 | } 15 | 16 | minecraft "com.mojang:minecraft:${project.minecraft_version_19}" 17 | mappings "net.fabricmc:yarn:${project.yarn_mappings_19}:v2" 18 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version_19}" 19 | 20 | modImplementation include(fabricApi.module("fabric-api-base", project.fabric_version_19)) 21 | modImplementation include(fabricApi.module("fabric-command-api-v2", project.fabric_version_19)) 22 | } 23 | 24 | processResources { 25 | inputs.property "version", project.version 26 | 27 | filesMatching("fabric.mod.json") { 28 | expand "version": project.version 29 | } 30 | 31 | 32 | } 33 | 34 | tasks.withType(JavaCompile).configureEach { 35 | it.options.release = 17 36 | } -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/FabricProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.utilitymods.friendapi.profiles.Affinity; 7 | import org.utilitymods.friendapi.profiles.Profile; 8 | import org.utilitymods.friendapi.profiles.ProfileFactory; 9 | 10 | /** 11 | * A factory for creating {@link Profile}s. 12 | * Allows you to use the raw player object to create a profile. 13 | */ 14 | public final class FabricProfileFactory extends ProfileFactory { 15 | 16 | /** 17 | * Singleton instance of the factory. 18 | */ 19 | public static final FabricProfileFactory INSTANCE = new FabricProfileFactory(); 20 | 21 | public Profile createProfile(@NotNull GameProfile profile, @NotNull Affinity affinity) { 22 | return createProfile(profile.getName(), profile.getId(), affinity); 23 | } 24 | 25 | public Profile createProfile(@NotNull GameProfile profile) { 26 | return createProfile(profile, Affinity.NEUTRAL); 27 | } 28 | 29 | public Profile createFriend(@NotNull GameProfile profile) { 30 | return createProfile(profile, Affinity.FRIEND); 31 | } 32 | 33 | public Profile createEnemy(@NotNull GameProfile profile) { 34 | return createProfile(profile, Affinity.ENEMY); 35 | } 36 | 37 | public Profile createProfile(@NotNull PlayerEntity player, @NotNull Affinity affinity) { 38 | return createProfile(player.getGameProfile(), affinity); 39 | } 40 | 41 | public Profile createProfile(@NotNull PlayerEntity player) { 42 | return createProfile(player.getGameProfile()); 43 | } 44 | 45 | public Profile createFriend(@NotNull PlayerEntity player) { 46 | return createFriend(player.getGameProfile()); 47 | } 48 | 49 | public Profile createEnemy(@NotNull PlayerEntity player) { 50 | return createEnemy(player.getGameProfile()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/FriendMod.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | //import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | import net.minecraft.command.argument.ArgumentTypes; 6 | import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; 7 | import org.utilitymods.friendapi.FriendManager; 8 | import org.utilitymods.friendapi.command.Commands; 9 | //import org.utilitymods.friendapi.command.FriendArgument; 10 | 11 | public class FriendMod implements ModInitializer { 12 | 13 | @Override 14 | public void onInitialize() { 15 | FriendManager.getInstance(); 16 | 17 | Commands commands = new Commands(); 18 | 19 | // ArgumentTypes.register("friendapi:friend", FriendArgument.class, new ConstantArgumentSerializer<>(FriendArgument::friendArgument)); 20 | // 21 | // ClientCommandManager.DISPATCHER.register(ClientCommandManager.literal("friendapi") 22 | // .then(commands.add) 23 | // .then(commands.remove) 24 | // .then(commands.list)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/command/AffinityArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.suggestion.Suggestions; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | import net.minecraft.command.CommandSource; 9 | import org.utilitymods.friendapi.profiles.Affinity; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.Objects; 14 | import java.util.concurrent.CompletableFuture; 15 | 16 | public class AffinityArgument implements ArgumentType { 17 | private static final Collection EXAMPLES = Arrays.asList("friend", "enemy", "neutral"); 18 | 19 | private AffinityArgument() { 20 | } 21 | 22 | public static AffinityArgument affinity() { 23 | return new AffinityArgument(); 24 | } 25 | 26 | public static Affinity getAffinity(CommandContext context, String name) { 27 | return context.getArgument(name, Affinity.class); 28 | } 29 | 30 | public Affinity parse(StringReader stringReader) { 31 | String string = stringReader.readUnquotedString(); 32 | Affinity affinity = Affinity.byName(string); 33 | return Objects.requireNonNullElse(affinity, Affinity.NEUTRAL); 34 | } 35 | 36 | @Override 37 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 38 | return CommandSource.suggestMatching(getExamples(), builder); 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/command/Commands.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.tree.LiteralCommandNode; 4 | //import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; 5 | //import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 6 | //import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.text.*; 9 | import net.minecraft.util.Formatting; 10 | import org.utilitymods.friendapi.FabricProfileFactory; 11 | import org.utilitymods.friendapi.profiles.Affinity; 12 | import org.utilitymods.friendapi.profiles.Profile; 13 | import org.utilitymods.friendapi.FriendManager; 14 | 15 | import java.util.Locale; 16 | 17 | import static com.mojang.brigadier.Command.SINGLE_SUCCESS; 18 | 19 | public class Commands { 20 | private static final String COMMAND_BASE = "/friendapi "; 21 | 22 | // public final LiteralCommandNode add = ClientCommandManager.literal("add") 23 | // .then(ClientCommandManager.argument("name", new PlayerArgument(MinecraftClient.getInstance())) 24 | // .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), Affinity.FRIEND)) 25 | // .then(ClientCommandManager.argument("affinity", AffinityArgument.affinity()) 26 | // .executes(context -> executeAdd(context.getSource(), PlayerArgument.getPlayer(context, "name"), AffinityArgument.getAffinity(context, "affinity")))) 27 | // ).build(); 28 | // 29 | // public final LiteralCommandNode remove = ClientCommandManager.literal("remove") 30 | // .then(ClientCommandManager.argument("name", new FriendArgument()) 31 | // .executes(context -> { 32 | // Profile player = context.getArgument("name", Profile.class); 33 | // FriendManager.getInstance().removeFriend(player.getUUID()); 34 | // context.getSource().sendFeedback(new TranslatableTextContent("commands.friendapi.removed", player.getName()).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, COMMAND_BASE + "add " + player.getName())).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableTextContent("commands.friendapi.add"))))); 35 | // return SINGLE_SUCCESS; 36 | // })).build(); 37 | // 38 | // public final LiteralCommandNode list = ClientCommandManager.literal("list") 39 | // .then(ClientCommandManager.literal("friends") 40 | // .executes(context -> executeList(context.getSource(), true, false))) 41 | // .then(ClientCommandManager.literal("enemies") 42 | // .executes(context -> executeList(context.getSource(), false, true))) 43 | // .executes(context -> executeList(context.getSource(), true, true)) 44 | // .build(); 45 | // 46 | // private int executeAdd(FabricClientCommandSource source, PlayerEntity player, Affinity affinity) { 47 | // if (FriendManager.getInstance().getAffinity(player.getGameProfile().getId()).equals(affinity)) { 48 | // 49 | // source.sendFeedback(new TranslatableTextContent("commands.friendapi.already", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableTextContent("commands.friendapi.remove"))))); 50 | // } else { 51 | // FriendManager.getInstance().addFriend(FabricProfileFactory.INSTANCE.createProfile(player, affinity)); 52 | // 53 | // source.sendFeedback(new TranslatableTextContent("commands.friendapi.now", player.getGameProfile().getName(), affinity.name().toLowerCase(Locale.ROOT)).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, player.getGameProfile().getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableTextContent("commands.friendapi.remove"))))); 54 | // } 55 | // return SINGLE_SUCCESS; 56 | // } 57 | // 58 | // private int executeList(FabricClientCommandSource source, boolean friends, boolean enemies) { 59 | // for (Profile profile : FriendManager.getInstance().getOnlyAllProfiles()) { 60 | // if (!(profile.getAffinity().equals(Affinity.FRIEND) && friends) && !(profile.getAffinity().equals(Affinity.ENEMY) && enemies)) 61 | // continue; 62 | // source.sendFeedback(getTextOfProfile(profile)); 63 | // } 64 | // return SINGLE_SUCCESS; 65 | // } 66 | 67 | // private Text getTextOfProfile(Profile profile) { 68 | // return new LiteralTextContent(getGrayedLabel("Name") + profile.getName() + " " + getGrayedLabel("Affinity") + getColoredAffinity(profile.getAffinity())).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, String.format("%sremove %s", COMMAND_BASE, profile.getName()))).withHoverEvent(new HoverEvent(net.minecraft.text.HoverEvent.Action.SHOW_TEXT, new TranslatableTextContent("commands.friendapi.remove")))); 69 | // } 70 | 71 | private String getGrayedLabel(String name) { 72 | return Formatting.GRAY + name + ": " + Formatting.RESET; 73 | } 74 | 75 | private String getColoredAffinity(Affinity affinity) { 76 | Formatting formatting = switch (affinity) { 77 | case NEUTRAL -> Formatting.YELLOW; 78 | case ENEMY -> Formatting.RED; 79 | case FRIEND -> Formatting.GREEN; 80 | }; 81 | return formatting + affinity.name().toLowerCase(Locale.ROOT) + Formatting.RESET; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/command/FriendArgument.java: -------------------------------------------------------------------------------- 1 | //package org.utilitymods.friendapi.command; 2 | // 3 | //import com.mojang.brigadier.StringReader; 4 | //import com.mojang.brigadier.arguments.ArgumentType; 5 | //import com.mojang.brigadier.context.CommandContext; 6 | //import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | //import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 8 | //import com.mojang.brigadier.suggestion.Suggestions; 9 | //import com.mojang.brigadier.suggestion.SuggestionsBuilder; 10 | //import net.minecraft.command.CommandSource; 11 | //import net.minecraft.text.LiteralTextContent; 12 | //import org.utilitymods.friendapi.profiles.Profile; 13 | //import org.utilitymods.friendapi.FriendManager; 14 | // 15 | //import java.util.Collection; 16 | //import java.util.concurrent.CompletableFuture; 17 | // 18 | //public class FriendArgument implements ArgumentType { 19 | // 20 | // public static FriendArgument friendArgument() { 21 | // return new FriendArgument(); 22 | // } 23 | // 24 | // private final DynamicCommandExceptionType invalidArg = new DynamicCommandExceptionType(o -> new LiteralTextContent(o + " is not a friend")); 25 | // 26 | // @Override 27 | // public Profile parse(StringReader reader) throws CommandSyntaxException { 28 | // String argument = reader.readString(); 29 | // Profile profile = null; 30 | // for (Profile s : FriendManager.getInstance().getFriendMapCopy().values()) { 31 | // if (s.getName().equalsIgnoreCase(argument)) { 32 | // profile = s; 33 | // break; 34 | // } 35 | // } 36 | // if (profile == null) throw invalidArg.create(argument); 37 | // return profile; 38 | // } 39 | // 40 | // @Override 41 | // public Collection getExamples() { 42 | // return FriendManager.getInstance().getFriendMapCopy().values().stream().map(Profile::getName).toList(); 43 | // } 44 | // 45 | // @Override 46 | // public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 47 | // return CommandSource.suggestMatching(getExamples(), builder); 48 | // } 49 | // 50 | //} -------------------------------------------------------------------------------- /FriendAPI-19/src/main/java/org/utilitymods/friendapi/command/PlayerArgument.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.command; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.arguments.ArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.suggestion.Suggestions; 8 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 9 | import net.minecraft.client.MinecraftClient; 10 | import net.minecraft.command.CommandSource; 11 | import net.minecraft.command.argument.EntityArgumentType; 12 | import net.minecraft.entity.player.PlayerEntity; 13 | 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | public class PlayerArgument implements ArgumentType { 19 | private static final Collection EXAMPLES = (Arrays.asList("Player", "ChiquitaV2", "Joe", "Dream")); 20 | private final MinecraftClient mc; 21 | 22 | public PlayerArgument(MinecraftClient mc) { 23 | this.mc = mc; 24 | } 25 | 26 | @Override 27 | public PlayerEntity parse(StringReader reader) throws CommandSyntaxException { 28 | String argument = reader.readString(); 29 | PlayerEntity playerEntity = null; 30 | assert mc.world != null; 31 | for (PlayerEntity p : mc.world.getPlayers()) { 32 | if (p.getDisplayName().getContent().toString().equalsIgnoreCase(argument)) { 33 | playerEntity = p; 34 | break; 35 | } 36 | } 37 | if (playerEntity == null) throw EntityArgumentType.NOT_ALLOWED_EXCEPTION.createWithContext(reader); 38 | return playerEntity; 39 | } 40 | 41 | @Override 42 | public Collection getExamples() { 43 | return EXAMPLES; 44 | } 45 | 46 | @Override 47 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 48 | if (context.getSource() instanceof CommandSource commandSource) { 49 | StringReader stringReader = new StringReader(builder.getInput()); 50 | stringReader.setCursor(builder.getStart()); 51 | 52 | return CommandSource.suggestMatching(commandSource.getPlayerNames(), builder); 53 | } else { 54 | return Suggestions.empty(); 55 | } 56 | 57 | } 58 | 59 | public static PlayerEntity getPlayer(CommandContext context, String name) { 60 | return context.getArgument(name, PlayerEntity.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FriendAPI-19/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "friendapi", 4 | "version": "${version}", 5 | "name": "FriendAPI", 6 | "description": "This library add support for the FriendAPI which allows for the same friends across client, versions, and instances", 7 | "authors": [ 8 | "Utility Mods" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/UtilityMods/FriendsAPI" 12 | }, 13 | "entrypoints": { 14 | "main": [ 15 | "org.utilitymods.friendapi.FriendMod" 16 | ] 17 | }, 18 | "license": "Apache-2.0", 19 | "icon": "assets/friendapi/icon.png", 20 | "depends": { 21 | "fabricloader": ">=0.14.7", 22 | "minecraft": "1.19.x" 23 | }, 24 | "custom": { 25 | "modmenu": { 26 | "badges": [ 27 | "library" 28 | ], 29 | "links": { 30 | "modmenu.discord": "https://discord.gg/h8EQyuYTK7" 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FriendAPI/build.gradle: -------------------------------------------------------------------------------- 1 | archivesBaseName = "FriendAPI" 2 | 3 | dependencies { 4 | api 'com.google.code.gson:gson:2.8.8' 5 | api 'org.apache.logging.log4j:log4j-core:2.14.1' 6 | api 'org.jetbrains:annotations:22.0.0' 7 | 8 | testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' 9 | } 10 | 11 | 12 | java { 13 | withSourcesJar() 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(8) 16 | } 17 | } 18 | 19 | test { 20 | useJUnitPlatform() 21 | } 22 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/FriendManager.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.utilitymods.friendapi.exceptions.ApiFailedException; 10 | import org.utilitymods.friendapi.profiles.Affinity; 11 | import org.utilitymods.friendapi.profiles.Profile; 12 | import org.utilitymods.friendapi.profiles.ProfileFactory; 13 | import org.utilitymods.friendapi.serialization.MapAdapter; 14 | 15 | import java.io.*; 16 | import java.lang.reflect.Type; 17 | import java.nio.charset.StandardCharsets; 18 | import java.nio.file.Files; 19 | import java.util.*; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * The FriendManager is the main class of the FriendAPI. It is responsible for managing the friends of a player. 25 | */ 26 | public final class FriendManager { 27 | 28 | /** 29 | * API Version 30 | */ 31 | private static final String VERSION = "1.1.0"; 32 | 33 | /** 34 | * The Logger. 35 | */ 36 | public static final Logger LOGGER = LogManager.getLogger("FriendAPI"); 37 | 38 | /** 39 | * The constant INSTANCE of the friend manager. 40 | */ 41 | private static final FriendManager INSTANCE = new FriendManager(); 42 | 43 | /** 44 | * A map of players' UUIDS to their friend class. 45 | */ 46 | private final ConcurrentHashMap friends = new ConcurrentHashMap<>(); 47 | 48 | /** 49 | * A map of players' UUIDS to the players they are neutral to, this is not saved and works as a cache; 50 | */ 51 | private final ConcurrentHashMap neutralCache = new ConcurrentHashMap<>(); 52 | 53 | /** 54 | * Gson Instance 55 | */ 56 | private final Gson gson = new GsonBuilder().enableComplexMapKeySerialization().registerTypeHierarchyAdapter(Map.class, new MapAdapter()).setPrettyPrinting().create(); 57 | 58 | /** 59 | * TypeToken 60 | */ 61 | private final Type type = new TypeToken>() {}.getType(); 62 | 63 | /** 64 | * Path to Json file 65 | */ 66 | private final File file = new File(System.getProperty("user.home"), ".friends.json"); 67 | 68 | /** 69 | * Default profile factory 70 | */ 71 | private ProfileFactory factory = new ProfileFactory(); 72 | 73 | /** 74 | * Instantiates a new Friend manager. 75 | */ 76 | private FriendManager() { 77 | long start = System.currentTimeMillis(); 78 | LOGGER.info("Using FriendAPI " + VERSION); 79 | 80 | load(); 81 | Runtime.getRuntime().addShutdownHook(new Thread(this::save)); 82 | 83 | LOGGER.info("FriendAPI started in " + (System.currentTimeMillis() - start) + "ms"); 84 | 85 | } 86 | 87 | /** 88 | * Gets the instance of the friend manager. 89 | */ 90 | public static FriendManager getInstance() { 91 | if (INSTANCE == null) 92 | throw new RuntimeException("FriendAPI accessed to early"); 93 | return INSTANCE; 94 | } 95 | 96 | /** 97 | * Loads friends.json into FRIENDS hashmap. 98 | */ 99 | public void load() { 100 | try { 101 | if (!file.exists()) { 102 | save(); 103 | } 104 | if (file.exists()) { 105 | Reader reader = Files.newBufferedReader(file.toPath()); 106 | ConcurrentHashMap tempList = gson.fromJson(reader, type); 107 | if (tempList != null) friends.putAll(tempList); 108 | reader.close(); 109 | } 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | LOGGER.fatal("Failed to load " + file.getAbsolutePath() + "!"); 113 | } 114 | } 115 | 116 | /** 117 | * Saves the FRIENDS hashmap into friends.json. 118 | */ 119 | public void save() { 120 | try { 121 | OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8); 122 | output.write(gson.toJson(friends)); 123 | output.close(); 124 | } catch (IOException e) { 125 | e.printStackTrace(); 126 | LOGGER.fatal("Failed to save \"" + file.getAbsolutePath() + "\"!"); 127 | } 128 | } 129 | 130 | /** 131 | * Gets friend map copy. 132 | * 133 | * @return a copy of the FRIENDS hashmap 134 | */ 135 | @NotNull 136 | public Map getFriendMapCopy() { 137 | return Collections.unmodifiableMap(friends); 138 | } 139 | 140 | /** 141 | * Gets a list with only your friends profiles 142 | * 143 | * @return list with only friends profiles 144 | */ 145 | @NotNull 146 | public List getOnlyFriendsProfiles() { 147 | return friends.values().stream().filter(Profile::isFriend).collect(Collectors.toList()); 148 | } 149 | 150 | /** 151 | * Gets a only your friends and enemies profiles 152 | * 153 | * @return list with only friends and enemies profiles 154 | */ 155 | @NotNull 156 | public Collection getOnlyAllProfiles() { 157 | return new HashSet<>(getFriendMapCopy().values()); 158 | } 159 | 160 | /** 161 | * Gets a friend from the FRIENDS hashmap. 162 | * 163 | * @param uuid the uuid of the player to search for 164 | * @return the friend 165 | */ 166 | @NotNull 167 | public Profile getFriend(@NotNull UUID uuid) { 168 | if (friends.containsKey(uuid)) { 169 | return friends.get(uuid); 170 | } else { 171 | return neutralCache.computeIfAbsent(uuid, k -> { 172 | try { 173 | return factory.createProfile(k); 174 | } catch (ApiFailedException e) { 175 | return factory.createProfile("empty", uuid); 176 | } 177 | }); 178 | } 179 | } 180 | 181 | /** 182 | * Add or replace a friend in the FRIENDS hashmap. 183 | * 184 | * @param profile the profile of the player to register 185 | * @return the newly created friend 186 | */ 187 | public Profile addFriend(Profile profile) { 188 | return friends.compute(profile.getUUID(), ((uuid, profile1) -> profile1 = profile)); 189 | } 190 | 191 | /** 192 | * Removes friend from the FRIENDS hashmap. 193 | * 194 | * @param uuid the uuid of the friend to remove 195 | */ 196 | public void removeFriend(@NotNull UUID uuid) { 197 | Profile profile = friends.get(uuid); 198 | if (profile != null) { 199 | friends.remove(uuid); 200 | profile.setAffinity(Affinity.NEUTRAL); 201 | neutralCache.compute(uuid, ((uuid1, baseProfile) -> baseProfile = profile)); 202 | } 203 | } 204 | 205 | /** 206 | * Gets friend type from the FRIENDS hashmap. 207 | * 208 | * @param uuid the uuid 209 | * @return the registered friend type 210 | */ 211 | @NotNull 212 | public Affinity getAffinity(@NotNull UUID uuid) { 213 | return getFriend(uuid).getAffinity(); 214 | } 215 | 216 | /** 217 | * Checks if player with specified UUID is a friend according to the FRIENDS hashmap. 218 | * 219 | * @param uuid the uuid of the player 220 | * @return whether the player queried is a registered friend 221 | */ 222 | public boolean isFriend(@NotNull UUID uuid) { 223 | return getAffinity(uuid) == Affinity.FRIEND; 224 | } 225 | 226 | /** 227 | * Checks if player with specified UUID is an enemy according to the FRIENDS hashmap. 228 | * 229 | * @param uuid the uuid of the player 230 | * @return whether the player queried is a registered enemy 231 | */ 232 | public boolean isEnemy(@NotNull UUID uuid) { 233 | return getAffinity(uuid) == Affinity.ENEMY; 234 | } 235 | 236 | /** 237 | * Checks if player with specified UUID is neutral or unregistered in the FRIENDS hashmap. 238 | * 239 | * @param uuid the uuid of the player 240 | * @return whether the player queried is neutral or unregistered 241 | */ 242 | public boolean isNeutral(@NotNull UUID uuid) { 243 | return getAffinity(uuid).type < 2; 244 | } 245 | 246 | /** 247 | * Attempts to add all friends from a name based friend list 248 | * 249 | * @param nameList List of the usernames you want to add to the friend list 250 | */ 251 | public void migrateFromNameList(List nameList) { 252 | nameList.forEach(name -> { 253 | try { 254 | addFriend(getFactory().createFriend(name)); 255 | } catch (ApiFailedException e) { 256 | e.printStackTrace(); 257 | } 258 | }); 259 | } 260 | 261 | /** 262 | * Attempts to add all friends from a uuid based friend list 263 | * 264 | * @param nameList List of player uuids you want to add to the friend list 265 | */ 266 | public void migrateFromUuidList(List nameList) { 267 | nameList.forEach(uuid -> { 268 | try { 269 | addFriend(getFactory().createFriend(uuid)); 270 | } catch (ApiFailedException e) { 271 | e.printStackTrace(); 272 | } 273 | }); 274 | } 275 | 276 | public ProfileFactory getFactory() { 277 | return factory; 278 | } 279 | 280 | public void setFactory(ProfileFactory factory) { 281 | this.factory = factory; 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/exceptions/ApiFailedException.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.exceptions; 2 | 3 | public class ApiFailedException extends Exception { 4 | 5 | public ApiFailedException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/profiles/Affinity.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.profiles; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.Locale; 6 | 7 | /** 8 | * Enum used to define affiliation with the friend 9 | */ 10 | public enum Affinity { 11 | 12 | FRIEND(0), 13 | 14 | NEUTRAL(1), 15 | 16 | ENEMY(2); 17 | 18 | public final int type; 19 | 20 | Affinity(int type) { 21 | this.type = type; 22 | } 23 | 24 | /** 25 | * Gets the affinity from the given string 26 | * 27 | * @param name The string to get the affinity from 28 | * @return The affinity 29 | */ 30 | @Nullable 31 | public static Affinity byName(@Nullable String name) { 32 | return name == null ? null : Affinity.valueOf(name.toUpperCase(Locale.ROOT)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/profiles/Profile.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.profiles; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import com.google.gson.annotations.SerializedName; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.utilitymods.friendapi.exceptions.ApiFailedException; 9 | 10 | import java.io.InputStreamReader; 11 | import java.io.Serializable; 12 | import java.net.HttpURLConnection; 13 | import java.net.URL; 14 | import java.util.Map; 15 | import java.util.UUID; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | /** 19 | * Profile class to store attributes of a friend. 20 | */ 21 | public class Profile implements Serializable { 22 | 23 | /** 24 | * Serialization ID. 25 | */ 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * The name of the Player 30 | */ 31 | @SerializedName("name") 32 | private String name; 33 | 34 | /** 35 | * The uuid of the Player 36 | */ 37 | @SerializedName("uuid") 38 | private final UUID uuid; 39 | 40 | /** 41 | * Your affinity to the Player 42 | */ 43 | @SerializedName("affinity") 44 | private Affinity affinity; 45 | 46 | /** 47 | * Custom data for the Profile 48 | */ 49 | @SerializedName("data") 50 | protected Map dataMap = new ConcurrentHashMap<>(); 51 | 52 | /** 53 | * Instantiates a new Profile provided name and uuid of friend. 54 | * 55 | * @param name the name of the friend 56 | * @param uuid the uuid of the friend 57 | * @param affinity the affinity with the friend 58 | */ 59 | public Profile(@NotNull String name, @NotNull UUID uuid, @NotNull Affinity affinity) { 60 | this.name = name; 61 | this.uuid = uuid; 62 | this.affinity = affinity; 63 | } 64 | 65 | /** 66 | * Instantiates a new Profile provided name and uuid of friend. 67 | * 68 | * @param name the name of the friend 69 | * @param uuid the uuid of the friend 70 | */ 71 | public Profile(@NotNull String name, @NotNull UUID uuid) { 72 | this.name = name; 73 | this.uuid = uuid; 74 | this.affinity = Affinity.FRIEND; 75 | } 76 | 77 | /** 78 | * Add custom data to the profile 79 | * 80 | * @param key The key its place in the map with. 81 | * Should follow a naming scheme of the client's name an underscore and the object's name. 82 | * Ex: WURST_ALIAS 83 | * @param data the dataObject you want to save 84 | */ 85 | //TODO: Add a way to remove data 86 | public Object editData(String key, Object data) { 87 | return dataMap.compute(key, ((key1, data1) -> data1 = data)); 88 | } 89 | 90 | /** 91 | * Gets data already in the list 92 | * 93 | * @param key The key the data is under 94 | * @return The object associated with the key 95 | */ 96 | public Object getData(String key) { 97 | return dataMap.get(key); 98 | } 99 | 100 | /** 101 | * Gets the name of the profile. 102 | * 103 | * @return The name of the profile. 104 | */ 105 | public String getName() { 106 | return name; 107 | } 108 | 109 | /** 110 | * Set's the name of the profile. 111 | * 112 | * @param name The name of the profile. 113 | */ 114 | public void setName(String name) { 115 | this.name = name; 116 | } 117 | 118 | /** 119 | * Get's the uuid of the profile. 120 | * 121 | * @return The uuid of the profile. 122 | */ 123 | public UUID getUUID() { 124 | return uuid; 125 | } 126 | 127 | /** 128 | * Get's the affinity of the profile. 129 | * 130 | * @return The affinity of the profile. 131 | */ 132 | public Affinity getAffinity() { 133 | return affinity; 134 | } 135 | 136 | /** 137 | * Set's the affinity of the profile. 138 | * 139 | * @param affinity The affinity of the profile. 140 | */ 141 | public void setAffinity(Affinity affinity) { 142 | this.affinity = affinity; 143 | } 144 | 145 | /** 146 | * Is the profile a friend? 147 | */ 148 | public boolean isFriend() { 149 | return getAffinity() == Affinity.FRIEND; 150 | } 151 | 152 | /** 153 | * Is the profile an enemy? 154 | */ 155 | public boolean isEnemy() { 156 | return getAffinity() == Affinity.ENEMY; 157 | } 158 | 159 | /** 160 | * Is the profile neutral? 161 | */ 162 | public boolean isNeutral() { 163 | return getAffinity() == Affinity.NEUTRAL; 164 | } 165 | 166 | @Override 167 | public String toString() { 168 | return "Name: " + name + "\n" + "Uuid: " + uuid + "\n" + "Affinity: " + affinity + "\n" + "Data: " + dataMap; 169 | } 170 | 171 | @Override 172 | public boolean equals(Object obj) { 173 | if (this == obj) { 174 | return true; 175 | } 176 | if (obj == null) { 177 | return false; 178 | } 179 | if (getClass() != obj.getClass()) { 180 | return false; 181 | } 182 | 183 | Profile profile = (Profile) obj; 184 | 185 | if (name == null) { 186 | if (profile.name != null) { 187 | return false; 188 | } 189 | } else if (!name.equals(profile.name)) { 190 | return false; 191 | } 192 | 193 | if (uuid == null) { 194 | if (profile.uuid != null) { 195 | return false; 196 | } 197 | } else if (!uuid.equals(profile.uuid)) { 198 | return false; 199 | } 200 | 201 | if (affinity == null) { 202 | return profile.affinity == null; 203 | } else return affinity.equals(profile.affinity); 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/profiles/ProfileFactory.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.profiles; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.utilitymods.friendapi.FriendManager; 8 | import org.utilitymods.friendapi.exceptions.ApiFailedException; 9 | 10 | import java.io.InputStreamReader; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | import java.util.UUID; 14 | 15 | public class ProfileFactory { 16 | 17 | public Profile createProfile(@NotNull String username, @NotNull UUID uuid, @NotNull Affinity affinity) { 18 | return new Profile(username, uuid, affinity); 19 | } 20 | 21 | public Profile createProfile(@NotNull String username, @NotNull UUID uuid) { 22 | return createProfile(username, uuid, Affinity.NEUTRAL); 23 | } 24 | 25 | public Profile createFriend(@NotNull String username, @NotNull UUID uuid) { 26 | return createProfile(username, uuid, Affinity.FRIEND); 27 | } 28 | 29 | public Profile createEnemy(@NotNull String username, @NotNull UUID uuid) { 30 | return createProfile(username, uuid, Affinity.ENEMY); 31 | } 32 | 33 | /** 34 | * Attempts to make a new base profile though getting the name from the mojang api 35 | * 36 | * @param uuid Uuid of the player you want a profile of 37 | * @param affinity the affinity with the profile 38 | * @return new BaseProfile based on the data from the api 39 | * @throws ApiFailedException if some part of the process fails throw an error 40 | */ 41 | public Profile createProfile(@NotNull UUID uuid, @NotNull Affinity affinity) throws ApiFailedException { 42 | try { 43 | URL url = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid.toString().replace("-", "")); 44 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 45 | JsonObject jsonObject = new JsonParser().parse(new InputStreamReader(conn.getInputStream())).getAsJsonObject(); 46 | String name = jsonObject.get("name").getAsString(); 47 | return createProfile(name, uuid, affinity); 48 | } catch (Exception e) { 49 | throw new ApiFailedException("no username associated with uuid:" + uuid); 50 | } 51 | } 52 | 53 | /** 54 | * Attempts to make a new neutral profile though getting the name from the mojang api 55 | * @param uuid Uuid of the player you want a profile of 56 | * @return new Profile with name and uuid based on the data from the api 57 | * @throws ApiFailedException when uuid is invalid 58 | */ 59 | public Profile createProfile(@NotNull UUID uuid) throws ApiFailedException { 60 | return createProfile(uuid, Affinity.NEUTRAL); 61 | } 62 | 63 | /** 64 | * Attempts to make a new friend profile though getting the name from the mojang api 65 | * @param uuid Uuid of the player you want a profile of 66 | * @return new Profile with name and uuid based on the data from the api 67 | * @throws ApiFailedException when name is invalid 68 | */ 69 | public Profile createFriend(@NotNull UUID uuid) throws ApiFailedException { 70 | return createProfile(uuid, Affinity.FRIEND); 71 | } 72 | 73 | /** 74 | * Attempts to make a new enemy profile though getting the name from the mojang api 75 | * @param uuid Uuid of the player you want a profile of 76 | * @return new Profile with name and uuid based on the data from the api 77 | * @throws ApiFailedException when name is invalid 78 | */ 79 | public Profile createEnemy(@NotNull UUID uuid) throws ApiFailedException { 80 | return createProfile(uuid, Affinity.ENEMY); 81 | } 82 | 83 | /** 84 | * Attempts to make a new profile though getting the uuid from the mojang api 85 | * @param username Username of the player you want a profile of 86 | * @param affinity the affinity with the profile 87 | * @return new Profile with name and uuid based on the data from the api 88 | * @throws ApiFailedException when name is invalid 89 | */ 90 | public Profile createProfile(@NotNull String username, @NotNull Affinity affinity) throws ApiFailedException { 91 | try { 92 | URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + username); 93 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 94 | JsonObject jsonObject = new JsonParser().parse(new InputStreamReader(conn.getInputStream())).getAsJsonObject(); 95 | UUID uuid = UUID.fromString(jsonObject.get("id").getAsString().replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5")); 96 | String name = jsonObject.get("name").getAsString(); 97 | return createProfile(name, uuid, affinity); 98 | } catch (Exception e) { 99 | throw new ApiFailedException("no uuid associated with " + username); 100 | } 101 | } 102 | 103 | /** 104 | * Attempts to make a new neutral profile though getting the uuid from the mojang api 105 | * @param username Username of the player you want a profile of 106 | * @return new Profile with name and uuid based on the data from the api 107 | * @throws ApiFailedException when name is invalid 108 | */ 109 | public Profile createProfile(@NotNull String username) throws ApiFailedException { 110 | return createProfile(username, Affinity.NEUTRAL); 111 | } 112 | 113 | /** 114 | * Attempts to make a new friend profile though getting the uuid from the mojang api 115 | * @param username Username of the player you want a profile of 116 | * @return new Profile with name and uuid based on the data from the api 117 | * @throws ApiFailedException when name is invalid 118 | */ 119 | public Profile createFriend(@NotNull String username) throws ApiFailedException { 120 | return createProfile(username, Affinity.FRIEND); 121 | } 122 | 123 | /** 124 | * Attempts to make a new enemy profile though getting the uuid from the mojang api 125 | * @param username Username of the player you want a profile of 126 | * @return new Profile with name and uuid based on the data from the api 127 | * @throws ApiFailedException when name is invalid 128 | */ 129 | public Profile createEnemy(@NotNull String username) throws ApiFailedException { 130 | return createProfile(username, Affinity.ENEMY); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /FriendAPI/src/main/java/org/utilitymods/friendapi/serialization/MapAdapter.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi.serialization; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonSerializationContext; 6 | import com.google.gson.JsonSerializer; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.Map; 10 | 11 | public class MapAdapter implements JsonSerializer> { 12 | 13 | @Override 14 | public JsonElement serialize(Map src, Type typeOfSrc, JsonSerializationContext context) { 15 | if (src == null || src.isEmpty()) 16 | return null; 17 | JsonObject obj = new JsonObject(); 18 | for (Map.Entry entry : src.entrySet()) { 19 | obj.add(entry.getKey().toString(), context.serialize(entry.getValue())); 20 | } 21 | return obj; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /FriendAPI/src/main/resources/assets/friendapi/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.friendapi.already": "%s is already a %s", 3 | "commands.friendapi.now": "%s is now a %s", 4 | "commands.friendapi.removed": "%s has been removed", 5 | "commands.friendapi.add": "add friend", 6 | "commands.friendapi.remove": "removed friend" 7 | } -------------------------------------------------------------------------------- /FriendAPI/src/test/java/org/utilitymods/friendapi/FriendManagerTest.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.utilitymods.friendapi.exceptions.ApiFailedException; 5 | import org.utilitymods.friendapi.profiles.Profile; 6 | 7 | import java.util.UUID; 8 | 9 | class FriendManagerTest { 10 | 11 | @Test 12 | void testFriendList() { 13 | FriendManager friendManager = FriendManager.getInstance(); 14 | System.out.println(friendManager.getFriendMapCopy()); 15 | System.out.println("Size: " + friendManager.getFriendMapCopy().size()); 16 | } 17 | 18 | @Test 19 | void testFactory() { 20 | FriendManager friendManager = FriendManager.getInstance(); 21 | UUID uuid = UUID.randomUUID(); 22 | Profile friend = friendManager.getFactory().createFriend("test", uuid); 23 | assert friend != null; 24 | assert friend.getName().equals("test"); 25 | assert friend.getUUID().equals(uuid); 26 | friendManager.addFriend(friend); 27 | assert friendManager.getFriendMapCopy().size() != 0; 28 | assert friendManager.isFriend(friend.getUUID()); 29 | friendManager.removeFriend(friend.getUUID()); 30 | } 31 | 32 | 33 | @Test 34 | void testUUIDProfile() { 35 | FriendManager friendManager = FriendManager.getInstance(); 36 | Profile friend = null; 37 | try { 38 | friend = friendManager.getFactory().createProfile(UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306")); 39 | } catch (ApiFailedException e) { 40 | e.printStackTrace(); 41 | } 42 | assert friend != null; 43 | assert friend.getName().equals("ChiquitaV2"); 44 | assert friend.getUUID().equals(UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306")); 45 | } 46 | 47 | @Test 48 | void testNameProfile() { 49 | FriendManager friendManager = FriendManager.getInstance(); 50 | Profile friend = null; 51 | try { 52 | friend = friendManager.getFactory().createProfile("ChiquitaV2"); 53 | } catch (ApiFailedException e) { 54 | e.printStackTrace(); 55 | } 56 | assert friend != null; 57 | assert friend.getName().equals("ChiquitaV2"); 58 | assert friend.getUUID().equals(UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306")); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /FriendAPI/src/test/java/org/utilitymods/friendapi/Main.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import org.utilitymods.friendapi.exceptions.ApiFailedException; 4 | import org.utilitymods.friendapi.profiles.Affinity; 5 | import org.utilitymods.friendapi.profiles.Profile; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | import java.util.stream.Collectors; 10 | 11 | public class Main { 12 | 13 | /* 14 | * Initialize tests 15 | */ 16 | public static void main(String[] args) { 17 | FriendManager friendManager = FriendManager.getInstance(); 18 | System.out.println(friendManager.getFriendMapCopy()); 19 | System.out.println("Size: " + friendManager.getFriendMapCopy().size()); 20 | addFriend(friendManager); 21 | addFriendWithData(friendManager); 22 | Profile isk = addEnemyWithMultiData(friendManager); 23 | 24 | System.out.println(friendManager.getFriend(isk.getUUID()).getData("info")); 25 | List friendslist = friendManager.getOnlyFriendsProfiles().stream().map(Profile::getName).collect(Collectors.toList()); 26 | System.out.println(friendslist); 27 | try { 28 | System.out.println(friendManager.getFactory().createFriend("Ethius")); 29 | System.out.println(friendManager.getFactory().createFriend(UUID.fromString("fb482eb2-9751-4479-b07c-df994cffef20"))); 30 | } catch (ApiFailedException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | /* 36 | * Add a friend 37 | */ 38 | private static Profile addFriend(FriendManager friendManager) { 39 | Profile profile = new Profile("ChiquitaV2", UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306")); 40 | return friendManager.addFriend(profile); 41 | } 42 | 43 | /* 44 | * Add a friend with data 45 | */ 46 | private static Profile addFriendWithData(FriendManager friendManager) { 47 | Profile profile = new Profile("DoleV2", UUID.fromString("c812b134-9147-48e6-9f04-6a0604deafd7")); 48 | ProfileData data = new ProfileData("ChiquitaV2", 69); 49 | profile.editData("info", data); 50 | return friendManager.addFriend(profile); 51 | } 52 | 53 | /* 54 | * Add an enemy with multiple data 55 | */ 56 | private static Profile addEnemyWithMultiData(FriendManager friendManager) { 57 | Profile profile = new Profile("ItsukiSumeragi", UUID.fromString("71fb3abc-f1dc-481d-a639-5631e7cd12ea"), Affinity.ENEMY); 58 | ProfileData data = new ProfileData("DoleV2", 1); 59 | profile.editData("info", data); 60 | profile.editData("notes", "Very based"); 61 | return friendManager.addFriend(profile); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /FriendAPI/src/test/java/org/utilitymods/friendapi/MigrationTest.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.UUID; 6 | 7 | public class MigrationTest { 8 | 9 | /* 10 | * Test the migration of the database. 11 | */ 12 | public static void main(String[] args) { 13 | FriendManager friendManager = FriendManager.getInstance(); 14 | MigrationTest migrationTest = new MigrationTest(); 15 | 16 | migrationTest.migrateUsernameList(friendManager); 17 | migrationTest.migrateUuidList(friendManager); 18 | } 19 | 20 | /* 21 | * Migrate the username list. 22 | */ 23 | private void migrateUsernameList(FriendManager friendManager) { 24 | List usernameList = new ArrayList<>(); 25 | usernameList.add("olliem5"); 26 | usernameList.add("Reap1"); 27 | usernameList.add("linustouchtips24"); 28 | 29 | friendManager.migrateFromNameList(usernameList); 30 | } 31 | 32 | /* 33 | * Migrate the uuid list. 34 | */ 35 | private void migrateUuidList(FriendManager friendManager) { 36 | List uuidList = new ArrayList<>(); 37 | uuidList.add(UUID.fromString("8f2a4be6-2717-42b9-a7ed-070489b49b56")); 38 | uuidList.add(UUID.fromString("5acf9e95-cde1-434d-a3e3-7573cec1dc53")); 39 | uuidList.add(UUID.fromString("10f3f5bd-88e0-442c-8d72-6ee44498d1fc")); 40 | 41 | friendManager.migrateFromUuidList(uuidList); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /FriendAPI/src/test/java/org/utilitymods/friendapi/ProfileData.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | public class ProfileData { 4 | 5 | /** 6 | * The name of the player. 7 | */ 8 | public final String alias; 9 | 10 | /** 11 | * The UUID of the player. 12 | */ 13 | public final Integer kills; 14 | 15 | /** 16 | * The data of the player. 17 | */ 18 | public ProfileData(String alias, Integer kills) { 19 | this.alias = alias; 20 | this.kills = kills; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Alias: " + alias + "\n" + "Kills: " + kills + "\n"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /FriendAPI/src/test/java/org/utilitymods/friendapi/Reflection.java: -------------------------------------------------------------------------------- 1 | package org.utilitymods.friendapi; 2 | 3 | import org.jetbrains.annotations.TestOnly; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.UUID; 7 | 8 | public class Reflection { 9 | 10 | public static void main(String[] args) { 11 | try { 12 | System.out.println("Starting test using just reflection"); 13 | Class c = Class.forName("org.utilitymods.friendapi.FriendManager"); 14 | Method getInstance = c.getMethod("getInstance"); 15 | Method getOnlyAllProfiles = c.getMethod("getOnlyAllProfiles"); 16 | Method isFriend = c.getMethod("isFriend", UUID.class); 17 | Method getFriend = c.getMethod("getFriend", UUID.class); 18 | Object api = getInstance.invoke(null); 19 | System.out.println("Listing all friends"); 20 | System.out.println(getOnlyAllProfiles.invoke(api)); 21 | System.out.println("Checking if friend"); 22 | System.out.println(isFriend.invoke(api, UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306"))); 23 | System.out.println("Getting friend profile"); 24 | System.out.println(getFriend.invoke(api, UUID.fromString("d738f0f0-85e4-4145-b735-3bc77396f306"))); 25 | 26 | 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'maven-publish' 4 | } 5 | 6 | allprojects { 7 | apply plugin: "java-library" 8 | apply plugin: "maven-publish" 9 | 10 | version = project.api_version + (System.getenv("CI") && !project.hasProperty("isRelase") ? ("-" + System.getenv("GITHUB_SHA").substring(0, 8)) : "") 11 | 12 | group = project.maven_group 13 | 14 | configurations { 15 | library 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | maven { 21 | name = 'Quantum' 22 | url = 'https://maven.quantumclient.org/snapshots' 23 | } 24 | maven { 25 | name = 'Fabric' 26 | url = 'https://maven.fabricmc.net/' 27 | } 28 | } 29 | 30 | task sourcesJar(type: Jar, dependsOn: classes) { 31 | classifier = "sources" 32 | from sourceSets.main.allSource 33 | } 34 | 35 | 36 | tasks.withType(JavaCompile).configureEach { 37 | it.options.encoding = "UTF-8" 38 | } 39 | 40 | publishing { 41 | publications { 42 | mavenJava(MavenPublication) { 43 | from components.java 44 | } 45 | } 46 | 47 | repositories { 48 | maven { 49 | name "Quantum-snapshots" 50 | url "https://maven.quantumclient.org/snapshots" 51 | credentials { 52 | username = System.getenv('MAVEN_USERNAME') 53 | password = System.getenv('MAVEN_PASSWORD') 54 | } 55 | authentication { 56 | basic(BasicAuthentication) 57 | } 58 | } 59 | maven { 60 | name "Quantum-releases" 61 | url "https://maven.quantumclient.org/releases" 62 | credentials { 63 | username = System.getenv('MAVEN_USERNAME') 64 | password = System.getenv('MAVEN_PASSWORD') 65 | } 66 | authentication { 67 | basic(BasicAuthentication) 68 | } 69 | ext.releases = true 70 | } 71 | maven { 72 | name = "GitHubPackages" 73 | url = uri("https://maven.pkg.github.com/UtilityMods/FriendAPI") 74 | credentials { 75 | username = System.getenv("GH_USERNAME") 76 | password = System.getenv("GH_TOKEN") 77 | } 78 | ext.releases = true 79 | } 80 | 81 | } 82 | } 83 | 84 | afterEvaluate { 85 | tasks.withType(PublishToMavenRepository).all { publishTask -> 86 | publishTask.onlyIf { task -> 87 | if (task.repository.hasProperty('releases') && !project.hasProperty("isRelase")) { 88 | task.enabled = false 89 | task.group = null 90 | return false 91 | } 92 | return true 93 | } 94 | } 95 | } 96 | 97 | jar { 98 | from { 99 | configurations.library.collect { 100 | it.isDirectory() ? it : zipTree(it) 101 | } 102 | } 103 | } 104 | 105 | } 106 | 107 | task buildAll(group: 'build') { 108 | subprojects.forEach { 109 | buildAll.dependsOn(it.tasks.sourcesJar) 110 | buildAll.dependsOn(it.tasks.build) 111 | } 112 | } 113 | 114 | task publishAll(group: 'publishing') { 115 | subprojects.forEach(p -> publishAll.dependsOn(p.tasks.publish)) 116 | } 117 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | # API Properties 4 | api_version=1.1.0 5 | maven_group=org.utilitymods.friendapi 6 | 7 | # Verisons for 1.16 implementation 8 | minecraft_version_16=1.16.5 9 | yarn_mappings_16=1.16.5+build.10 10 | loader_version_16=0.11.3 11 | 12 | fabric_version_16=0.42.0+1.16 13 | 14 | # Verisons for 1.17 implementation 15 | minecraft_version_17=1.17.1 16 | yarn_mappings_17=1.17.1+build.10 17 | loader_version_17=0.11.6 18 | 19 | fabric_version_17=0.35.0+1.17 20 | 21 | # Verisons for 1.18 implementation 22 | minecraft_version_18=1.18 23 | yarn_mappings_18=1.18+build.1 24 | loader_version_18=0.12.8 25 | 26 | fabric_version_18=0.43.1+1.18 27 | 28 | # Verisons for 1.19 implementation 29 | minecraft_version_19=1.19 30 | yarn_mappings_19=1.19+build.1 31 | loader_version_19=0.14.7 32 | 33 | fabric_version_19=0.55.3+1.19 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UtilityMods/FriendAPI/bca50917f60fdebce60e81be2f2a75bc1f99c59c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | } 9 | } 10 | 11 | include 'FriendAPI' 12 | 13 | include 'FriendAPI-16' 14 | include 'FriendAPI-17' 15 | include 'FriendAPI-12' 16 | include 'FriendAPI-18' 17 | include 'FriendAPI-19' 18 | 19 | --------------------------------------------------------------------------------